/*
 * Copyright (C) 2003-2007 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.platform.organization.integration;

import java.util.*;

import javax.jcr.Session;

import org.picocontainer.Startable;

import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.xml.*;
import org.exoplatform.management.annotations.*;
import org.exoplatform.management.jmx.annotations.NameTemplate;
import org.exoplatform.management.jmx.annotations.Property;
import org.exoplatform.management.rest.annotations.RESTEndpoint;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.*;
import org.exoplatform.services.organization.externalstore.IDMExternalStoreService;
import org.exoplatform.services.organization.idm.PicketLinkIDMCacheService;
import org.exoplatform.services.organization.impl.*;

/**
 * This Service create Organization Model profiles, for User and Groups not
 * created via eXo OrganizationService.
 * 
 * @deprecated OrganizationIntegrationService is replaced by External Store API
 * @author Boubaker KHANFIR
 */
@Deprecated
@Managed
@ManagedDescription("Platform Organization Model Integration Service")
@NameTemplate({ @Property(key = "name", value = "OrganizationIntegrationService"),
    @Property(key = "service", value = "extensions"), @Property(key = "type", value = "platform") })
@RESTEndpoint(path = "orgsync")
public class OrganizationIntegrationService implements Startable {

  private static final Log                                                       LOG                         =
                                                                                     ExoLogger.getLogger(OrganizationIntegrationService.class);

  private static final Comparator<org.exoplatform.container.xml.ComponentPlugin> COMPONENT_PLUGIN_COMPARATOR =
                                                                                                             new Comparator<org.exoplatform.container.xml.ComponentPlugin>() {
                                                                                                                                                                                                                          public int compare(org.exoplatform.container.xml.ComponentPlugin o1,
                                                                                                                                                                                                                                             org.exoplatform.container.xml.ComponentPlugin o2) {
                                                                                                                                                                                                                            return o1.getPriority()
                                                                                                                                                                                                                                - o2.getPriority();
                                                                                                                                                                                                                          }
                                                                                                                                                                                                                        };

  public static final Comparator<Group>                                          GROUP_COMPARATOR            =
                                                                                                  new Comparator<Group>() {
                                                                                                                                                                                                               public int compare(Group o1,
                                                                                                                                                                                                                                  Group o2) {
                                                                                                                                                                                                                 if (o1.getId()
                                                                                                                                                                                                                       .contains(o2.getId())) {
                                                                                                                                                                                                                   return 1;
                                                                                                                                                                                                                 }
                                                                                                                                                                                                                 if (o2.getId()
                                                                                                                                                                                                                       .contains(o1.getId())) {
                                                                                                                                                                                                                   return -1;
                                                                                                                                                                                                                 }
                                                                                                                                                                                                                 return o2.getId()
                                                                                                                                                                                                                          .compareTo(o1.getId());
                                                                                                                                                                                                               }
                                                                                                                                                                                                             };

  private Map<String, UserEventListener>                                         userDAOListeners_;

  private Map<String, GroupEventListener>                                        groupDAOListeners_;

  private Map<String, MembershipEventListener>                                   membershipDAOListeners_;

  private Map<String, UserProfileEventListener>                                  userProfileListeners_;

  private OrganizationService                                                    organizationService;

  private RepositoryService                                                      repositoryService;

  private PortalContainer                                                        container;

  private boolean                                                                synchronizeGroups           = false;

  private PicketLinkIDMCacheService                                              picketLinkIDMCacheService;

  private IDMExternalStoreService                                                externalStoreService;

  private boolean                                                                enabled                     = true;

  public OrganizationIntegrationService(OrganizationService organizationService,
                                        IDMExternalStoreService externalStoreService,
                                        RepositoryService repositoryService,
                                        ConfigurationManager manager,
                                        PortalContainer container,
                                        InitParams initParams,
                                        PicketLinkIDMCacheService picketLinkIDMCacheService) {
    LOG.warn("OrganizationIntegrationService is depricated. It was replaced by External Store API.");
    this.organizationService = organizationService;
    this.repositoryService = repositoryService;
    this.container = container;
    this.picketLinkIDMCacheService = picketLinkIDMCacheService;
    this.externalStoreService = externalStoreService;
    userDAOListeners_ = new LinkedHashMap<String, UserEventListener>();
    groupDAOListeners_ = new LinkedHashMap<String, GroupEventListener>();
    membershipDAOListeners_ = new LinkedHashMap<String, MembershipEventListener>();
    userProfileListeners_ = new LinkedHashMap<String, UserProfileEventListener>();
    boolean hasExternalComponentPlugins = false;
    int nbExternalComponentPlugins = 0;
    try {
      ExternalComponentPlugins organizationServiceExternalComponentPlugins =
                                                                           manager.getConfiguration()
                                                                                  .getExternalComponentPlugins(OrganizationIntegrationService.class.getName());

      if (organizationServiceExternalComponentPlugins != null
          && organizationServiceExternalComponentPlugins.getComponentPlugins() != null) {
        nbExternalComponentPlugins = organizationServiceExternalComponentPlugins.getComponentPlugins().size();
      }

      Component organizationServiceComponent = manager.getComponent(OrganizationIntegrationService.class);

      if (organizationServiceComponent != null && organizationServiceComponent.getComponentPlugins() != null) {
        nbExternalComponentPlugins += organizationServiceComponent.getComponentPlugins().size();
      }
      hasExternalComponentPlugins = (nbExternalComponentPlugins > 0);
    } catch (Exception e) {
      LOG.error("Test if this component has ExternalComponentPlugins generated an exception", e);
    }

    if (!hasExternalComponentPlugins) {
      try {
        ExternalComponentPlugins organizationServiceExternalComponentPlugins =
                                                                             manager.getConfiguration()
                                                                                    .getExternalComponentPlugins(OrganizationService.class.getName());
        addComponentPlugin(organizationServiceExternalComponentPlugins.getComponentPlugins());

        Component organizationServiceComponent = manager.getComponent(OrganizationService.class);
        List<org.exoplatform.container.xml.ComponentPlugin> organizationServicePlugins =
                                                                                       organizationServiceComponent.getComponentPlugins();
        if (organizationServicePlugins != null) {
          addComponentPlugin(organizationServicePlugins);
        }
      } catch (Exception e) {
        LOG.error("Failed to add OrganizationService plugins", e);
      }
    } else {
      if (LOG.isDebugEnabled()) {
        LOG.debug("This component has already " + nbExternalComponentPlugins + " ExternalComponentPlugins");
      }
    }
    if (initParams != null) {
      if (initParams.containsKey("workspace")) {
        Util.WORKSPACE = initParams.getValueParam("workspace").getValue();
      } else {
        LOG.warn("'workspace' init param is empty, use default value: " + Util.WORKSPACE);
      }
      if (initParams.containsKey("synchronizeGroups")) {
        synchronizeGroups = Boolean.parseBoolean(initParams.getValueParam("synchronizeGroups").getValue());
      } else {
        LOG.warn("'synchronizeGroups' init param is empty, use default value: false");
      }
      if (initParams.containsKey("homePath")) {
        Util.HOME_PATH = initParams.getValueParam("homePath").getValue();
      } else {
        LOG.warn("'homePath' init param is empty, use default value: " + Util.HOME_PATH);
      }
    } else {
      LOG.warn("init params not set, use default values for 'homePath'[=" + Util.HOME_PATH + "] and 'workspace[=" + Util.WORKSPACE
          + "]'");
    }
  }

  public void start() {
    enabled = !externalStoreService.isEnabled();
    if (!isEnabled()) {
      LOG.warn("ExternalStoreService is enabled, thus OrganizationIntegrationService will be disabled. You can remove the configuration of OrganizationIntegrationService that is deprecated.");
      return;
    }
    Session session = null;
    try {
      session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
      Util.init(session);

      if (synchronizeGroups) {
        // Search for Groups that aren't yet integrated
        syncAllGroups(EventType.ADDED.toString());

        // Search for Groups that are deleted from Organization Datasource
        syncAllGroups(EventType.DELETED.toString());
      }
    } catch (Exception e) {
      LOG.error(e.getMessage(), e);
    } finally {
      if (session != null) {
        session.logout();
      }
    }
  }

  public void stop() {
  }

  /**
   * Determines whether OrganizationIntegrationService is enabled or not
   * 
   * @return
   */
  public boolean isEnabled() {
    return enabled;
  }

  /**
   * Add a list of OrganizationService listeners into
   * OrganizationIntegrationService
   * 
   * @param plugins List of OrganizationService ComponentPlugins
   */
  public void addComponentPlugin(List<org.exoplatform.container.xml.ComponentPlugin> plugins) {
    if (plugins == null)
      return;
    Collections.sort(plugins, COMPONENT_PLUGIN_COMPARATOR);
    for (org.exoplatform.container.xml.ComponentPlugin plugin : plugins) {
      try {
        Class<?> pluginClass = Class.forName(plugin.getType());
        ComponentPlugin cplugin = (ComponentPlugin) container.createComponent(pluginClass, plugin.getInitParams());
        cplugin.setName(plugin.getName());
        cplugin.setDescription(plugin.getDescription());

        this.addListenerPlugin(cplugin);
      } catch (Exception e) {
        LOG.error("Failed to instanciate component plugin " + plugin.getName() + ", type=" + plugin.getClass(), e);
      }
    }
  }

  /**
   * Add a listener instance to dedicated list of one organization element.
   * 
   * @param listener have to extends UserEventListener, GroupEventListener,
   *          MembershipEventListener or UserProfileEventListener.
   */
  public void addListenerPlugin(ComponentPlugin listener) {
    if (listener instanceof OrganizationServiceInitializer) {
      return;
    } else if (listener instanceof UserEventListener) {
      userDAOListeners_.put(listener.getName(), (UserEventListener) listener);
    } else if (listener instanceof GroupEventListener) {
      groupDAOListeners_.put(listener.getName(), (GroupEventListener) listener);
    } else if (listener instanceof MembershipEventListener) {
      membershipDAOListeners_.put(listener.getName(), (MembershipEventListener) listener);
    } else if (listener instanceof UserProfileEventListener) {
      userProfileListeners_.put(listener.getName(), (UserProfileEventListener) listener);
    } else {
      LOG.warn("Unknown listener type : " + listener.getClass());
    }
  }

  /**
   * Apply OrganizationService listeners on all Groups
   */
  @Managed
  @ManagedDescription("invoke all organization model listeners. Becarefull, this could takes a lot of time.")
  @Impact(ImpactType.READ)
  public void syncAll() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("All groups, users, profiles and memberships listeners invocation.");
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    startRequest();
    LOG.info("The synchronization is started for all LDAP users and groups, eventType: ADDED,DELETED");
    try {

      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for integrated Groups.");
      }
      syncAllGroups(EventType.UPDATED.toString());
      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for non integrated Groups.");
      }
      syncAllGroups(EventType.ADDED.toString());
      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for deleted Groups, but remain integrated.");
      }

      syncAllGroups(EventType.DELETED.toString());

      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for integrated Users.");
      }
      syncAllUsers(EventType.UPDATED.toString());
      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for non integrated Users.");
      }
      syncAllUsers(EventType.ADDED.toString());
      if (LOG.isDebugEnabled()) {
        LOG.debug(" Search for deleted Users, but remain integrated.");
      }
      syncAllUsers(EventType.DELETED.toString());
    } catch (Exception e) {
      LOG.error(e.getMessage(), e);
    }
    LOG.info("The synchronization is finished for all LDAP users and groups, eventType: ADDED,DELETED");
    endRequest();
  }

  /**
   * Invoke Groups listeners to all Organization Model Elements
   * 
   * @param eventType ADDED/DELETED/UPDATED
   * @throws Exception JCR or IDM operation failure
   */
  @Managed
  @ManagedDescription("invoke all groups listeners")
  @Impact(ImpactType.READ)
  public void syncAllGroups(@ManagedDescription("Scan for added or deleted groups") @ManagedName("eventType") String eventType) throws Exception {
    if (LOG.isDebugEnabled()) {
      LOG.debug("All Groups listeners invocation, operation= " + eventType);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    startRequest();

    List<Group> groups = new ArrayList<Group>(organizationService.getGroupHandler().getAllGroups());
    // Invoke listeners on groups, starting from parent groups to children
    Collections.sort(groups, GROUP_COMPARATOR);

    picketLinkIDMCacheService.invalidateAll();
    EventType event = EventType.valueOf(eventType);
    switch (event) {
    case DELETED: {
      LOG.info("The synchronization is started for all LDAP groups, eventType: DELETED");
      // Invoke delete listeners on groups, starting from children groups
      // to parent
      Collections.reverse(groups);
      // Search for deleted groups, and invoke
      // GroupEventListener#preDelete and #postDelete
      Session session = null;
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedGroups = Util.getActivatedGroups(session);
        for (Group group : groups) {
          activatedGroups.remove(group.getId());
        }
        for (String groupId : activatedGroups) {
          syncGroup(groupId, eventType);
        }
      } finally {
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP groups, eventType: DELETED");
      break;
    }
    case UPDATED: {
      LOG.info("The synchronization is started for all LDAP groups, eventType: UPDATED");
      // Search for added groups that aren't yet integrated
      Session session = null;
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedGroups = Util.getActivatedGroups(session);
        for (String groupId : activatedGroups) {
          syncGroup(groupId, eventType);
        }
      } finally {
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP groups, eventType: UPDATED");
      break;
    }
    case ADDED: {
      LOG.info("The synchronization is started for all LDAP groups, eventType: ADDED");
      // Search for added groups that aren't yet integrated
      Session session = null;
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedGroups = Util.getActivatedGroups(session);
        for (Group group : groups) {
          if (!activatedGroups.contains(group.getId())) {
            syncGroup(group.getId(), eventType);
          }
        }
      } finally {
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP groups, eventType: ADDED");
      break;
    }
    }
    endRequest();
  }

  /**
   * Apply OrganizationService listeners on a selected group.
   * 
   * @param groupId The group Identifier
   * @param eventType ADDED/UPDATED/DELETED
   */
  @Managed
  @ManagedDescription("invoke a group listeners")
  @Impact(ImpactType.READ)
  public void syncGroup(@ManagedDescription("Group Id") @ManagedName("groupId") String groupId,
                        @ManagedDescription("Event type ADDED, UPDATED or DELETED") @ManagedName("eventType") String eventType) {

    if (LOG.isDebugEnabled()) {
      LOG.debug("\tGroup listeners invocation, operation= " + eventType + ", for group= " + groupId);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    EventType event = EventType.valueOf(eventType);
    // Invalidate plidmcache
    picketLinkIDMCacheService.invalidateAll();
    switch (event) {
    case DELETED: {
      {
        Session session = null;
        startRequest();
        try {
          session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);

          List<Group> groups = Util.getActivatedChildrenGroup(session, groupId);

          Collections.sort(groups, GROUP_COMPARATOR);
          Collections.reverse(groups);
          for (Group group : groups) {
            invokeDeleteGroupListeners(group.getId());
          }
          invokeDeleteGroupListeners(groupId);
        } catch (Exception e) {
          LOG.error("Error during recovery of activated chidren group ", e);
        } finally {
          endRequest();
          if (session != null) {
            session.logout();
          }
        }
      }
      break;
    }
    case ADDED:
    case UPDATED: {
      startRequest();
      try {
        Group group = organizationService.getGroupHandler().findGroupById(groupId);
        if (group == null) {
          LOG.warn("\t\t" + groupId + " group wasn't found.");
          return;
        }
        invokeListenersToSavedGroup(group, event.equals(EventType.ADDED));
      } catch (Exception e) {
        LOG.error("\t\t" + "Error occurred while invoking listeners of group: " + groupId, e);
      } finally {
        endRequest();
      }
      break;
    }
    }
  }

  /**
   * Apply all users OrganizationService listeners
   * 
   * @param eventType ADDED/UPDATED/DELETED
   */
  @Managed
  @ManagedDescription("invoke all users listeners")
  @Impact(ImpactType.READ)
  public void syncAllUsers(@ManagedDescription("Event type: added/updated/deleted") @ManagedName("eventType") String eventType) {

    if (LOG.isDebugEnabled()) {
      LOG.debug("All users listeners invocation, eventType = " + eventType);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    EventType event = EventType.valueOf(eventType);
    Session session = null;
    picketLinkIDMCacheService.invalidateAll();
    switch (event) {
    case DELETED: {
      LOG.info("The synchronization is started for all LDAP users, eventType: DELETED");
      if (LOG.isDebugEnabled()) {
        LOG.debug("\tSearch for deleted users and invoke related listeners.");
      }
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedUsers = Util.getActivatedUsers(session);

        ListAccess<User> usersListAccess = organizationService.getUserHandler().findAllUsers();

        int i = 0;
        int k = 0;
        while (i <= usersListAccess.getSize()) {
          int length = i + 10 <= usersListAccess.getSize() ? 10 : usersListAccess.getSize() - i;
          User[] users = usersListAccess.load(i, length);
          for (User user : users) {
            activatedUsers.remove(user.getUserName());
            k++;
            if (k % 100 == 0) {
              LOG.info(k + " users are checked for deletion");
            }
          }
          i += 10;
        }
        if (k % 100 != 0) {
          LOG.info(k + " users are checked for deletion");
        }
        for (String username : activatedUsers) {
          syncUser(username, eventType);
        }
      } catch (Exception e) {
        LOG.error("\t\tUnknown error occurred while preparing to proceed users deletion", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP users, eventType: DELETED");
      break;
    }
    case UPDATED: {
      LOG.info("The synchronization is started for all LDAP users, eventType: UPDATED");
      if (LOG.isDebugEnabled()) {
        LOG.debug("\tAll users update invocation: Search for already existing users in Datasource and that are already integrated.");
      }
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedUsers = Util.getActivatedUsers(session);
        for (String username : activatedUsers) {
          syncUser(username, eventType);
        }
      } catch (Exception e) {
        LOG.error("\tUnknown error occurred while preparing to proceed user update", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP users, eventType: UPDATED");
      break;
    }
    case ADDED: {
      LOG.info("The synchronization is started for all LDAP users, eventType: ADDED");
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<String> activatedUsers = Util.getActivatedUsers(session);

        if (LOG.isDebugEnabled()) {
          LOG.debug("\tAll new users intagration: Search for already existing users in Datasource but not integrated yet.");
        }
        ListAccess<User> usersListAccess = organizationService.getUserHandler().findAllUsers();
        int i = 0;
        int k = 0;
        while (i <= usersListAccess.getSize()) {
          int length = i + 10 <= usersListAccess.getSize() ? 10 : usersListAccess.getSize() - i;
          User[] users = usersListAccess.load(i, length);
          for (User user : users) {
            if (!activatedUsers.contains(user.getUserName())) {
              syncUser(user.getUserName(), eventType);
            }
            k++;
            if (k % 100 == 0) {
              LOG.info(k + " users are checked for addition");
            }
          }
          i += 10;
        }
        if (k % 100 != 0) {
          LOG.info(k + " users are checked for addition");
        }
      } catch (Exception e) {
        LOG.error("\tUnknown error occurred while preparing to proceed user update", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      LOG.info("The synchronization is finished for all LDAP users, eventType: ADDED");
      break;
    }
    }
  }

  /**
   * Apply OrganizationService listeners on selected User
   * 
   * @param username The user name
   * @param eventType ADDED/UPDATED/DELETED
   */
  @SuppressWarnings("deprecation")
  @Managed
  @ManagedDescription("invoke a user listeners")
  @Impact(ImpactType.READ)
  public void syncUser(@ManagedDescription("User name") @ManagedName("username") String username,
                       @ManagedDescription("Event type") @ManagedName("eventType") String eventType) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("\tUser listeners invocation, operation= " + eventType + ", for user= " + username);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    EventType event = EventType.valueOf(eventType);
    // Invalidate plidmcache
    picketLinkIDMCacheService.invalidateAll();
    switch (event) {
    case DELETED: {
      User user = null;
      startRequest();
      try {
        user = organizationService.getUserHandler().findUserByName(username);
      } catch (Exception e) {
        LOG.warn("\t\tError occurred while verifying if user is present in Datasource. The operation will be interrupted.", e);
        return;
      } finally {
        endRequest();
      }
      if (user != null) {
        LOG.warn("\t\tUser exists: can't invoke delete listeners on the existant user : " + username);
        return;
      }
      invokeUserMembershipsListeners(username, event);
      invokeUserProfileListeners(username, event);
      Session session = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        if (Util.hasUserFolder(session, username)) {
          LOG.info("Invoke user deletion: " + username);
          user = new UserImpl(username);
          Collection<UserEventListener> userDAOListeners = userDAOListeners_.values();
          for (UserEventListener userEventListener : userDAOListeners) {
            startRequest();
            try {
              userEventListener.preDelete(user);
            } catch (Exception e) {
              LOG.error("\t\tFailed to call preDelete on " + username + " User with listener : " + userEventListener.getClass(),
                        e);
            } finally {
              endRequest();
            }
            startRequest();
            try {
              userEventListener.postDelete(user);
            } catch (Exception e) {
              LOG.error("\t\tFailed to call postDelete on " + username + " User with listener : " + userEventListener.getClass(),
                        e);
            } finally {
              endRequest();
            }
          }
        }
      } catch (Exception e) {
        LOG.error("\t\tFailed to delete " + username + " User", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    case ADDED:
    case UPDATED: {
      Session session = null;
      startRequest();
      try {
        boolean isNew = event.equals(EventType.ADDED);
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        if (!isNew || !Util.hasUserFolder(session, username)) {
          User user = organizationService.getUserHandler().findUserByName(username);
          if (user == null) {
            LOG.info("\t\tFailed to synchronize " + username + " : Doesn't exist ");
            break;
          }
          if (user.getCreatedDate() == null) {
            user.setCreatedDate(new Date());
          }
          LOG.info("Invoke " + username + " user synchronization ");
          Collection<UserEventListener> userDAOListeners = userDAOListeners_.values();
          for (UserEventListener userEventListener : userDAOListeners) {
            startRequest();
            try {
              userEventListener.preSave(user, isNew);
            } catch (Exception e) {
              LOG.warn("\t\tFailed to call preSave for " + username + " User with listener : " + userEventListener.getClass(), e);
            } finally {
              endRequest();
            }
          }
          for (UserEventListener userEventListener : userDAOListeners) {
            try {
              startRequest();
              userEventListener.postSave(user, isNew);
            } catch (Exception e) {
              LOG.warn("\t\tFailed to call postSave for " + username + " User with listener : " + userEventListener.getClass(),
                       e);
            } finally {
              endRequest();
            }
          }
        }
      } catch (Exception e) {
        LOG.warn("\t\tFailed to call listeners for " + username + " User", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      invokeUserProfileListeners(username, event);
      invokeUserMembershipsListeners(username, event);
      break;
    }
    }
  }

  /**
   * Apply OrganizationService listeners on selected User
   * 
   * @param username The user name
   */
  @Managed
  @ManagedDescription("Test if User is synhronized")
  @Impact(ImpactType.READ)
  public String isUserSync(@ManagedDescription("User name") @ManagedName("username") String username) throws Exception {
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    Session session = null;
    try {
      session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
      return "" + Util.hasUserFolder(session, username);
    } finally {
      if (session != null) {
        session.logout();
      }
    }
  }

  /**
   * trigger ADD,UPDATE or DELETE membership listeners. The membership is
   * identified by the username, groupId and membershipType.
   * 
   * @param username
   * @param groupId
   * @param eventType
   */
  @Managed
  @ManagedDescription("invoke a membership listeners")
  @Impact(ImpactType.READ)
  public void syncMembership(@ManagedDescription("User name") @ManagedName("username") String username,
                             @ManagedDescription("group identifier") @ManagedName("groupId") String groupId,
                             @ManagedDescription("event type") @ManagedName("eventType") String eventType) {

    if (LOG.isDebugEnabled()) {
      LOG.debug("Memberships listeners invocation, operation= " + eventType + ", for membership=" + username + ":" + groupId);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    EventType event = EventType.valueOf(eventType);
    switch (event) {
    case DELETED: {
      Session session = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<Membership> activatedMemberships = Util.getActivatedMembershipsRelatedToUser(session, username);
        // Select memberships with given username and groupId
        int i = 0;
        while (i < activatedMemberships.size()) {
          Membership membership = activatedMemberships.get(i);
          if (membership.getGroupId().equals(groupId)) {
            activatedMemberships.remove(i);
          } else {
            i++;
          }
        }
        if (activatedMemberships.isEmpty()) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("No integrated memberships was found for user: " + username + ", and group = " + groupId);
          }
          return;
        }
        List<Membership> memberships = null;
        try {
          memberships = (List<Membership>) organizationService.getMembershipHandler().findMembershipsByUserAndGroup(username,
                                                                                                                    groupId);
        } catch (Exception e) {
          LOG.error("\t\tError occurred while verifying if membership is present in Datasource or not. This may not cause a problem :"
              + e.getMessage());
        }
        if (memberships == null) {
          memberships = new ArrayList<Membership>();
        }

        for (Membership membership : activatedMemberships) {
          if (!contains(membership, memberships)) {
            invokeMembershipListeners(username, groupId, membership.getMembershipType(), event);
          }
        }
      } catch (Exception e) {
        LOG.error("\t\tUnknown error occurred while preparing to proceed membership deletion", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    case ADDED:
    case UPDATED: {
      boolean isNew = EventType.ADDED.equals(event);
      Session session = null;
      startRequest();
      try {
        List<Membership> memberships = null;
        try {
          memberships = (List<Membership>) organizationService.getMembershipHandler().findMembershipsByUserAndGroup(username,
                                                                                                                    groupId);
        } catch (Exception e) {
          LOG.error("\t\tError occurred while verifying if membership is present in Datasource or not. This may not cause a problem :"
              + e.getMessage());
        }
        if (memberships == null || memberships.isEmpty()) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("No integrated memberships was found for user: " + username + ", and group = " + groupId);
          }
          return;
        }

        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        List<Membership> activatedMemberships = Util.getActivatedMembershipsRelatedToUser(session, username);
        // Select memberships with given username and groupId
        int i = 0;
        while (i < activatedMemberships.size()) {
          Membership membership = activatedMemberships.get(i);
          if (membership.getGroupId().equals(groupId)) {
            activatedMemberships.remove(i);
          } else {
            i++;
          }
        }
        if (!isNew && activatedMemberships.isEmpty()) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("No integrated memberships was found for user: " + username + ", abd group = " + groupId);
          }
          return;
        }

        for (Membership membership : memberships) {
          if (isNew) {
            if (!contains(membership, activatedMemberships)) {
              invokeMembershipListeners(username, groupId, membership.getMembershipType(), event);
            }
          } else {
            if (contains(membership, activatedMemberships)) {
              invokeMembershipListeners(username, groupId, membership.getMembershipType(), event);
            }
          }
        }
      } catch (Exception e) {
        LOG.error("\t\tUnknown error occurred while preparing to proceed membership deletion", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    }
  }

  private void invokeMembershipListeners(String username, String groupId, String membershipType, EventType eventType) {

    if (LOG.isDebugEnabled()) {
      LOG.debug("\tMembership listeners invocation, operation= " + eventType + ", for membership= " + membershipType + ":"
          + username + ":" + groupId);
    }
    if (!isEnabled()) {
      throw new IllegalStateException("OrganizationIntegrationService is disabled");
    }

    switch (eventType) {
    case DELETED: {
      startRequest();
      try {
        Membership membership = null;
        try {
          membership = organizationService.getMembershipHandler().findMembershipByUserGroupAndType(username,
                                                                                                   groupId,
                                                                                                   membershipType);
        } catch (Exception e) {
          LOG.warn("\t\tError occurred while verifying if membership is present in Datasource or not. This may not cause a problem :"
              + e.getMessage());
        } finally {
          endRequest();
        }
        if (membership != null) {
          LOG.warn("\t\tMembership exists: can't invoke delete listeners on the existant membership : " + membership.getId());
          return;
        }
        {
          membership = new MembershipImpl();
          ((MembershipImpl) membership).setGroupId(groupId);
          ((MembershipImpl) membership).setUserName(username);
          ((MembershipImpl) membership).setMembershipType(membershipType);
          ((MembershipImpl) membership).setId(Util.computeId(membership));
        }
        try {
          LOG.info("Invoke " + membership.getId() + " Membership deletion listeners.");
          Collection<MembershipEventListener> membershipDAOListeners = membershipDAOListeners_.values();
          for (MembershipEventListener membershipEventListener : membershipDAOListeners) {
            startRequest();
            try {
              membershipEventListener.preDelete(membership);
            } catch (Exception e) {
              LOG.error("\t\tFailed to call preDelete on " + username + " Membership (" + membership.getId() + ") listener = "
                  + membershipEventListener.getClass(), e);
            } finally {
              endRequest();
            }
            startRequest();
            try {
              membershipEventListener.postDelete(membership);
            } catch (Exception e) {
              LOG.error("\t\tFailed to call postDelete on " + username + " Membership (" + membership.getId() + ") listener = "
                  + membershipEventListener.getClass(), e);
            } finally {
              endRequest();
            }
          }
        } catch (Exception e) {
          LOG.error("\t\tFailed to call listeners on Membership (" + membership.getId() + ")", e);
        }
      } catch (Exception e) {
        LOG.error("\t\tUnknown error occurred while preparing to proceed membership deletion", e);
      } finally {
        endRequest();
      }
      break;
    }
    case ADDED:
    case UPDATED: {
      boolean isNew = EventType.ADDED.equals(eventType);
      Session session = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        Membership membership = organizationService.getMembershipHandler()
                                                   .findMembershipByUserGroupAndType(username, groupId, membershipType);
        try {
          if (!Util.hasGroupFolder(session, groupId)) {
            syncGroup(groupId, EventType.ADDED.toString());
          }
          if (!Util.hasUserFolder(session, username)) {
            syncUser(username, EventType.ADDED.toString());
          }
          if (membership != null && (!isNew || !Util.hasMembershipFolder(session, membership))) {
            LOG.info("Invoke " + membership.getId() + " Membership synchronization.");
            Collection<MembershipEventListener> membershipDAOListeners = membershipDAOListeners_.values();
            for (MembershipEventListener membershipEventListener : membershipDAOListeners) {
              startRequest();
              try {
                membershipEventListener.preSave(membership, isNew);
              } catch (Exception e) {
                LOG.error("\t\tFailed to call preSave on Membership (" + membership.getId() + ",isNew = " + isNew
                    + ") listener = " + membershipEventListener.getClass(), e);
              } finally {
                endRequest();
              }
              startRequest();
              try {
                membershipEventListener.postSave(membership, isNew);
              } catch (Exception e) {
                LOG.error("\t\tFailed to call postSave on Membership (" + membership.getId() + ") listener = "
                    + membershipEventListener.getClass(), e);
              } finally {
                endRequest();
              }
            }
          }
        } catch (Exception e) {
          LOG.error("\t\tFailed to call listeners on Membership (" + membership.getId() + ")", e);
        }
      } catch (Exception e) {
        LOG.error("\t\tFailed to call listeners on " + username + " Memberships listeners", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    }
  }

  private void invokeDeleteGroupListeners(String groupId) {
    try {
      Group group = organizationService.getGroupHandler().findGroupById(groupId);
      if (group != null) {
        LOG.warn("\t\tGroup exists: can't invoke delete listeners on the existant group : " + groupId);
        return;
      }
    } catch (Exception exception) {
      LOG.error("\t\tException while trying to get a group instance with id : " + groupId
          + ", it may has been already synchronized:" + exception.getMessage());
    }

    Session session = null;
    try {
      session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
      if (!Util.hasGroupFolder(session, groupId)) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("\t\tGroup doesn't exist or has been already deleted.");
        }
        return;
      }
      List<Membership> memberships = Util.getActivatedMembershipsRelatedToGroup(session, groupId);
      for (Membership membership : memberships) {
        // Memberships could be managed internally in eXo Datasource,
        // so synchronize with the remote Datasources by removing all
        // memberships of deleted group
        Membership tmpMembership = organizationService.getMembershipHandler().removeMembership(membership.getId(), true);
        if (tmpMembership == null) { // if the Membership doesn't exist
                                     // in datasource, then it's not
                                     // deleted, and so listeners
                                     // aren't triggered, in this case
                                     // force the listeners execution
          invokeMembershipListeners(membership.getUserName(),
                                    membership.getGroupId(),
                                    membership.getMembershipType(),
                                    EventType.DELETED);
        }
      }

    } catch (Exception exception) {
      LOG.error("\t\tCouldn't process deletion of Memberships related to the group : " + groupId, exception);
    } finally {
      endRequest();
      if (session != null) {
        session.logout();
      }
    }
    GroupImpl group = new GroupImpl(groupId);
    group.setId(groupId);
    Collection<GroupEventListener> groupDAOListeners = groupDAOListeners_.values();
    LOG.info("Invoke " + groupId + " Group deletion listeners.");
    for (GroupEventListener groupEventListener : groupDAOListeners) {
      try {
        groupEventListener.preDelete(group);
      } catch (Exception e) {
        LOG.warn("\t\tFailed to call preDelete on " + group.getId() + " Group, listener = " + groupEventListener.getClass(), e);
      }
      try {
        groupEventListener.postDelete(group);
      } catch (Exception e) {
        LOG.warn("\t\tFailed to call postDelete on " + group.getId() + " Group, listener = " + groupEventListener.getClass(), e);
      }
    }
  }

  private void invokeUserMembershipsListeners(String username, EventType eventType) {

    if (LOG.isDebugEnabled()) {
      LOG.debug("\t\tMemberships listeners invocation, operation= " + eventType + ", for user= " + username);
    }
    switch (eventType) {
    case DELETED: {
      Session session = null;
      startRequest();
      try {
        Collection<?> userMemberships = null;
        try {
          userMemberships = organizationService.getMembershipHandler().removeMembershipByUser(username, true);
        } catch (Exception exception) {
          LOG.error("\t\t\tCouldn't process deletion of Memberships related to the user : " + username, exception);
        }
        if (userMemberships != null) {
          session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
          List<Membership> memberships = Util.getActivatedMembershipsRelatedToUser(session, username);
          for (Membership membership : memberships) {
            invokeMembershipListeners(username, membership.getGroupId(), membership.getMembershipType(), eventType);
          }
        } else { // Delete all related Memberships
          LOG.error("\t\t\tUser " + username + " was deleted, but some memberships are always existing : " + userMemberships);
        }
      } catch (Exception e) {
        LOG.error("\t\t\tUnknown error occurred while preparing to proceed membership deletion", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    case ADDED:
    case UPDATED: {
      boolean isNew = EventType.ADDED.equals(eventType);
      Session session = null;
      Collection<?> memberships = null;
      List<Membership> activatedMemberships = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        memberships = organizationService.getMembershipHandler().findMembershipsByUser(username);
        activatedMemberships = Util.getActivatedMembershipsRelatedToUser(session, username);
        for (Object membershipObject : memberships) {
          Membership membership = (Membership) membershipObject;
          boolean isAlreadyIntegrated = contains(membership, activatedMemberships);
          if (isNew) {
            if (!isAlreadyIntegrated) {
              invokeMembershipListeners(username, membership.getGroupId(), membership.getMembershipType(), eventType);
            } else {
              if (LOG.isDebugEnabled()) {
                LOG.debug("\t\t\t" + membership.getId() + " Membership is already integrated");
              }
            }
          } else {
            if (isAlreadyIntegrated) {
              invokeMembershipListeners(username, membership.getGroupId(), membership.getMembershipType(), eventType);
            } else {
              if (LOG.isDebugEnabled()) {
                LOG.debug("\t\t\t" + membership.getId()
                    + " Membership is not yet added, add invoke listeners with parameter isNew=true");
              }
              invokeMembershipListeners(username, membership.getGroupId(), membership.getMembershipType(), EventType.ADDED);
            }
          }
        }
      } catch (Exception e) {
        LOG.error("\t\t\tFailed to call Membership listeners for user : " + username, e);
        throw new IllegalStateException(e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    }
  }

  private boolean contains(Membership membership, List<Membership> activatedMemberships) {
    if (membership == null || activatedMemberships == null || activatedMemberships.size() == 0) {
      return false;
    }
    for (Membership tmpMembership : activatedMemberships) {
      if (tmpMembership.getId().equals(membership.getId())) {
        return true;
      }
    }
    return false;
  }

  private void invokeUserProfileListeners(String username, EventType eventType) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("\t\tProfile listeners invocation, operation= " + eventType + ", for user= " + username);
    }
    switch (eventType) {
    case ADDED:
    case UPDATED: {
      Session session = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        boolean isNew = EventType.ADDED.equals(eventType);
        UserProfile userProfile = organizationService.getUserProfileHandler().findUserProfileByName(username);
        if (userProfile == null) {
          userProfile = organizationService.getUserProfileHandler().createUserProfileInstance(username);
          organizationService.getUserProfileHandler().saveUserProfile(userProfile, isNew);
          userProfile = organizationService.getUserProfileHandler().findUserProfileByName(username);
        }
        if (!isNew || !Util.hasProfileFolder(session, username)) {
          LOG.info("Invoke " + username + " user profile synchronization.");
          Collection<UserProfileEventListener> userProfileListeners = userProfileListeners_.values();
          for (UserProfileEventListener userProfileEventListener : userProfileListeners) {
            if (userProfile.getUserInfoMap() == null) {
              userProfile.setUserInfoMap(new HashMap<String, String>());
            }
            startRequest();
            try {
              userProfileEventListener.preSave(userProfile, isNew);
            } catch (Exception e) {
              LOG.warn("\t\t\tFailed to call preSave on " + username + " User profile with listener : "
                  + userProfileEventListener.getClass(), e);
            } finally {
              endRequest();
            }
            startRequest();
            try {
              userProfileEventListener.postSave(userProfile, isNew);
            } catch (Exception e) {
              LOG.warn("\t\t\tFailed to call postSave on " + username + " User profile with listener : "
                  + userProfileEventListener.getClass(), e);
            } finally {
              endRequest();
            }
          }
        }
      } catch (Exception e) {
        LOG.warn("\t\t\tFailed to call listeners on " + username + " User profile", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    case DELETED: {
      Session session = null;
      startRequest();
      try {
        session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
        UserProfile userProfile = null;
        try {
          userProfile = organizationService.getUserProfileHandler().findUserProfileByName(username);
        } catch (Exception e) {
          LOG.warn("\t\t\tError occurred while verifying if userProfile is present in Datasource or not. This may not cause a problem :"
              + e.getMessage());
        } finally {
          endRequest();
        }
        startRequest();
        if (userProfile != null) {
          organizationService.getUserProfileHandler().removeUserProfile(username, true);
        } else if (Util.hasProfileFolder(session, username)) {
          LOG.info("Invoke " + username + " user profile deletion listeners.");
          userProfile = new UserProfileImpl(username);
          userProfile.setUserInfoMap(new HashMap<String, String>());
          Collection<UserProfileEventListener> userProfileListeners = userProfileListeners_.values();
          for (UserProfileEventListener userProfileEventListener : userProfileListeners) {
            startRequest();
            try {
              userProfileEventListener.preDelete(userProfile);
            } catch (Exception e) {
              LOG.warn("\t\t\tFailed to call preSave on " + username + " User profile with listener : "
                  + userProfileEventListener.getClass(), e);
            } finally {
              endRequest();
            }
            startRequest();
            try {
              userProfileEventListener.postDelete(userProfile);
            } catch (Exception e) {
              LOG.warn("\t\t\tFailed to call postSave on " + username + " User profile with listener : "
                  + userProfileEventListener.getClass(), e);
            } finally {
              endRequest();
            }
          }
        }
      } catch (Exception e) {
        LOG.warn("\t\t\tFailed to call listeners on " + username + " User profile", e);
      } finally {
        endRequest();
        if (session != null) {
          session.logout();
        }
      }
      break;
    }
    }
  }

  private void invokeListenersToSavedGroup(Group group, boolean isNew) {
    Session session = null;
    try {
      session = repositoryService.getCurrentRepository().getSystemSession(Util.WORKSPACE);
      if (group.getParentId() != null && !group.getParentId().isEmpty()) {
        try {
          Group parentGroup = organizationService.getGroupHandler().findGroupById(group.getParentId());
          invokeListenersToSavedGroup(parentGroup, isNew);
        } catch (Exception e) {
          LOG.warn("\t\tError occurred while attempting to get parent of " + group.getId()
              + " Group. Listeners will not be applied on parent " + group.getParentId(), e);
        }
      }
      if (!isNew || !Util.hasGroupFolder(session, group.getId())) {
        LOG.info("Invoke " + group.getId() + " Group save listeners.");
        Collection<GroupEventListener> groupDAOListeners = groupDAOListeners_.values();
        for (GroupEventListener groupEventListener : groupDAOListeners) {
          try {
            groupEventListener.preSave(group, isNew);
          } catch (Exception e) {
            LOG.warn("\t\t\tFailed to call preSave on " + group.getId() + " Group, listener = " + groupEventListener.getClass(),
                     e);
          }
          try {
            groupEventListener.postSave(group, isNew);
          } catch (Exception e) {
            LOG.warn("\t\t\tFailed to call postSave on " + group.getId() + " Group, listener = " + groupEventListener.getClass(),
                     e);
          }
        }
      }
    } catch (Exception e) {
      LOG.warn("\t\t\tFailed to call listeners for " + group.getId() + " Group.", e);
    } finally {
      if (session != null) {
        session.logout();
      }
    }
  }

  private void endRequest() {
    if (organizationService instanceof ComponentRequestLifecycle) {
      try {
        ((ComponentRequestLifecycle) organizationService).endRequest(container);
      } catch (Exception e) {
        LOG.warn("Error while committing and rollbacking transaction, see below for root cause", e);
      }
    }
  }

  private void startRequest() {
    if (organizationService instanceof ComponentRequestLifecycle) {
      ((ComponentRequestLifecycle) organizationService).startRequest(container);
    }
  }

}
