org.silverpeas.core.admin.service.Admin.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.core.admin.service.Admin.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * 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.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.silverpeas.core.admin.service;

import org.apache.commons.lang3.time.DurationFormatUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.silverpeas.core.admin.RightProfile;
import org.silverpeas.core.admin.component.ApplicationResourcePasting;
import org.silverpeas.core.admin.component.ComponentInstanceDeletion;
import org.silverpeas.core.admin.component.ComponentInstancePostConstruction;
import org.silverpeas.core.admin.component.ComponentInstancePreDestruction;
import org.silverpeas.core.admin.component.WAComponentRegistry;
import org.silverpeas.core.admin.component.model.*;
import org.silverpeas.core.admin.domain.DomainDriver;
import org.silverpeas.core.admin.domain.DomainDriverManager;
import org.silverpeas.core.admin.domain.driver.ldapdriver.LDAPSynchroUserItf;
import org.silverpeas.core.admin.domain.model.Domain;
import org.silverpeas.core.admin.domain.model.DomainCache;
import org.silverpeas.core.admin.domain.model.DomainProperty;
import org.silverpeas.core.admin.domain.synchro.SynchroDomainReport;
import org.silverpeas.core.admin.domain.synchro.SynchroDomainScheduler;
import org.silverpeas.core.admin.domain.synchro.SynchroGroupReport;
import org.silverpeas.core.admin.domain.synchro.SynchroGroupScheduler;
import org.silverpeas.core.admin.persistence.AdminPersistenceException;
import org.silverpeas.core.admin.quota.exception.QuotaException;
import org.silverpeas.core.admin.quota.model.Quota;
import org.silverpeas.core.admin.service.cache.AdminCache;
import org.silverpeas.core.admin.service.cache.TreeCache;
import org.silverpeas.core.admin.space.SpaceAndChildren;
import org.silverpeas.core.admin.space.SpaceInst;
import org.silverpeas.core.admin.space.SpaceInstLight;
import org.silverpeas.core.admin.space.SpaceProfileInst;
import org.silverpeas.core.admin.space.SpaceProfileInstManager;
import org.silverpeas.core.admin.space.SpaceServiceProvider;
import org.silverpeas.core.admin.space.model.Space;
import org.silverpeas.core.admin.space.notification.SpaceEventNotifier;
import org.silverpeas.core.admin.space.quota.ComponentSpaceQuotaKey;
import org.silverpeas.core.admin.space.quota.DataStorageSpaceQuotaKey;
import org.silverpeas.core.admin.user.GroupManager;
import org.silverpeas.core.admin.user.ProfileInstManager;
import org.silverpeas.core.admin.user.ProfiledObjectManager;
import org.silverpeas.core.admin.user.UserManager;
import org.silverpeas.core.admin.user.constant.UserAccessLevel;
import org.silverpeas.core.admin.user.constant.UserState;
import org.silverpeas.core.admin.user.dao.GroupSearchCriteriaForDAO;
import org.silverpeas.core.admin.user.dao.SearchCriteriaDAOFactory;
import org.silverpeas.core.admin.user.dao.UserSearchCriteriaForDAO;
import org.silverpeas.core.admin.user.model.*;
import org.silverpeas.core.backgroundprocess.AbstractBackgroundProcessRequest;
import org.silverpeas.core.backgroundprocess.BackgroundProcessTask;
import org.silverpeas.core.contribution.contentcontainer.content.ContentManager;
import org.silverpeas.core.contribution.contentcontainer.content.ContentManagerException;
import org.silverpeas.core.i18n.I18NHelper;
import org.silverpeas.core.index.indexing.model.FullIndexEntry;
import org.silverpeas.core.index.indexing.model.IndexEngineProxy;
import org.silverpeas.core.notification.system.ResourceEvent;
import org.silverpeas.core.persistence.Transaction;
import org.silverpeas.core.persistence.jdbc.DBUtil;
import org.silverpeas.core.util.Process;
import org.silverpeas.core.util.*;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.core.util.logging.Level;
import org.silverpeas.core.util.logging.SilverLogger;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.transaction.Transactional;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.text.MessageFormat.format;
import static java.util.Collections.singletonList;
import static org.silverpeas.core.SilverpeasExceptionMessages.*;
import static org.silverpeas.core.admin.domain.DomainDriver.ActionConstants.ACTION_MASK_MIXED_GROUPS;
import static org.silverpeas.core.util.StringUtil.isLong;

/**
 * The class Admin is the main class of the Administrator.
 * <p>
 * The role of the administrator is to create and maintain user domains, spaces,
 * component instances, and the user/group roles on that spaces and component instances.
 * </p>
 */
@Singleton
@Transactional(rollbackOn = AdminException.class)
class Admin implements Administration {

    /**
     * The unique identifier of the main administrator (root) in Silverpeas. It is hard configured in
     * Silverpeas.
     */
    private static final String ADMIN_ID = "0";

    /**
     * Text to pass in the exception's messages
     */
    private static final String REMOVE_OF = "Suppression de ";
    private static final String INDEX_SPACE_SCOPE = "Spaces";
    private static final String INDEX_COMPONENT_SCOPE = "Components";
    private static final String SPACE = "space";
    private static final String SUBSPACES_OF_SPACE = "subspaces of space ";
    private static final String COMPONENT = "component";
    private static final String PROFILE = "profile";
    private static final String SPACE_PROFILE = "space profile";
    private static final String GROUP = "group";
    private static final String ACCESSIBLE_BY_USER = "accessible by user ";
    private static final String USER = "user ";
    private static final String IN_GROUP = "in group ";
    private static final String GROUP_PROFILE = "group profile";
    private static final String DOMAIN_ID_PARAM = "domainId";
    private static final String LOGIN_PARAM = "login";
    private static final String DOMAIN = "domain";
    private static final String COMPONENTS_IN_SPACE = "components in space ";
    private static final String AVAILABLE_TO_USER = "available to user ";
    private static final String ADMIN_SYNCHRONIZE_GROUP = "admin.synchronizeGroup";
    private static final String ADMIN_SYNCHRONIZE_DOMAIN = "admin.synchronizeSilverpeasWithDomain";
    private static final String ADMIN_SYNCHRONIZE_USERS = "admin.synchronizeUsers";
    private static final String ADMIN_SYNCHRONIZE_GROUPS = "admin.synchronizeGroups";
    private static final String ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS = "admin.checkOutGroups";
    private static final String ID_IS = " (id:";

    // Divers
    private final Object semaphore = new Object();
    private boolean delUsersOnDiffSynchro = true;
    private boolean shouldFallbackGroupNames = true;
    private boolean shouldFallbackUserLogins = false;
    private String groupSynchroCron = "";
    private String domainSynchroCron = "";
    private String senderEmail = null;
    private String senderName = null;
    private SynchroGroupScheduler groupSynchroScheduler = null;
    private SynchroDomainScheduler domainSynchroScheduler = null;
    private SettingBundle roleMapping = null;
    private boolean useProfileInheritance = false;

    @Inject
    private AdminCache cache;
    @Inject
    private WAComponentRegistry componentRegistry;
    @Inject
    private UserManager userManager;
    @Inject
    private GroupManager groupManager;
    @Inject
    private SpaceInstManager spaceManager;
    @Inject
    private ProfiledObjectManager profiledObjectManager;
    @Inject
    private ProfileInstManager profileManager;
    @Inject
    private GroupProfileInstManager groupProfileManager;
    @Inject
    private ComponentInstManager componentManager;
    @Inject
    private SpaceProfileInstManager spaceProfileManager;
    @Inject
    private SpaceEventNotifier spaceEventNotifier;
    @Inject
    private ContentManager contentManager;
    @Inject
    private DomainDriverManager domainDriverManager;
    @Inject
    private DomainCache domainCache;
    @Inject
    private TreeCache treeCache;
    @Inject
    private GroupCache groupCache;

    private void setup() {
        // Load silverpeas admin resources
        SettingBundle resources = ResourceLocator.getSettingBundle("org.silverpeas.admin.admin");
        roleMapping = ResourceLocator.getSettingBundle("org.silverpeas.admin.roleMapping");
        useProfileInheritance = resources.getBoolean("UseProfileInheritance", false);
        senderEmail = resources.getString("SenderEmail");
        senderName = resources.getString("SenderName");
        shouldFallbackGroupNames = resources.getBoolean("FallbackGroupNames", true);
        shouldFallbackUserLogins = resources.getBoolean("FallbackUserLogins", false);
        domainSynchroCron = resources.getString("DomainSynchroCron", "* 4 * * *");
        groupSynchroCron = resources.getString("GroupSynchroCron", "* 5 * * *");
        delUsersOnDiffSynchro = resources.getBoolean("DelUsersOnThreadedSynchro", true);
        // Cache management
        cache.setCacheAvailable(StringUtil.getBooleanValue(resources.getString("UseCache", "1")));
    }

    protected Admin() {
        // Hidden constructor
    }

    @PostConstruct
    private void initialize() {
        setup();
        Transaction.performInOne(() -> {
            this.reloadCache();
            return null;
        });
    }

    @Override
    public void reloadCache() {
        cache.resetCache();
        treeCache.clearCache();
        groupCache.clearCache();
        try {

            List<SpaceInstLight> spaces = spaceManager.getAllSpaces();
            for (SpaceInstLight space : spaces) {
                addSpaceInTreeCache(space, false);
            }

        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
        }
    }

    @Override
    public void initSynchronization() {
        // init synchronization of domains
        List<String> synchroDomainIds = new ArrayList<>();
        Domain[] domains = null;
        try {
            domains = domainDriverManager.getAllDomains();
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
        }
        if (domains != null) {
            for (Domain domain : domains) {
                DomainDriver synchroDomain;
                try {
                    synchroDomain = domainDriverManager.getDomainDriver(domain.getId());
                    if (synchroDomain != null && synchroDomain.isSynchroThreaded()) {
                        synchroDomainIds.add(domain.getId());
                    }
                } catch (Exception e) {
                    SilverLogger.getLogger(this).error(e);
                }
            }
        }
        domainSynchroScheduler = new SynchroDomainScheduler();
        domainSynchroScheduler.initialize(domainSynchroCron, synchroDomainIds);

        // init synchronization of groups
        List<GroupDetail> groups = null;
        try {
            groups = getSynchronizedGroups();
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
        }
        if (groups != null) {
            List<String> synchronizedGroupIds = groups.stream().filter(GroupDetail::isSynchronized)
                    .map(GroupDetail::getId).collect(Collectors.toList());
            groupSynchroScheduler = new SynchroGroupScheduler();
            groupSynchroScheduler.initialize(groupSynchroCron, synchronizedGroupIds);
        }
    }

    private void addSpaceInTreeCache(SpaceInstLight space, boolean addSpaceToSuperSpace) throws AdminException {
        Space spaceInCache = new Space();
        spaceInCache.setSpaceInstLight(space);
        List<ComponentInstLight> components = componentManager.getComponentsInSpace(space.getLocalId());
        spaceInCache.setComponents(components);

        List<SpaceInstLight> subSpaces = getSubSpaces(space.getId());

        spaceInCache.setSubspaces(subSpaces);
        treeCache.addSpace(space.getLocalId(), spaceInCache);

        for (SpaceInstLight subSpace : subSpaces) {
            addSpaceInTreeCache(subSpace, false);
        }

        if (addSpaceToSuperSpace && !space.isRoot()) {
            treeCache.addSubSpace(Integer.parseInt(space.getFatherId()), space);
        }
    }

    // -------------------------------------------------------------------------
    // SPACE RELATED FUNCTIONS
    // -------------------------------------------------------------------------

    @Override
    public void createSpaceIndex(int spaceId) {
        try {
            SpaceInstLight space = getSpaceInstLight(spaceId);
            createSpaceIndex(space);
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
        }
    }

    @Override
    public void createSpaceIndex(SpaceInstLight spaceInst) {
        // Index the space
        String spaceId = spaceInst.getId();
        FullIndexEntry indexEntry = new FullIndexEntry(INDEX_SPACE_SCOPE, "Space", spaceId);
        indexEntry.setTitle(spaceInst.getName());
        indexEntry.setPreview(spaceInst.getDescription());
        indexEntry.setCreationUser(String.valueOf(spaceInst.getCreatedBy()));
        indexEntry.setCreationDate(spaceInst.getCreateDate());
        indexEntry.setLastModificationUser(String.valueOf(spaceInst.getUpdatedBy()));
        indexEntry.setLastModificationDate(spaceInst.getUpdateDate());
        IndexEngineProxy.addIndexEntry(indexEntry);
    }

    @Override
    public void deleteSpaceIndex(SpaceInst spaceInst) {
        String spaceId = spaceInst.getId();
        FullIndexEntry indexEntry = new FullIndexEntry(INDEX_SPACE_SCOPE, "Space", spaceId);
        IndexEngineProxy.removeIndexEntry(indexEntry.getPK());
    }

    @Override
    public void deleteAllSpaceIndexes() {
        IndexEngineProxy.removeScopedIndexEntries(INDEX_SPACE_SCOPE);
    }

    @Override
    public String addSpaceInst(String userId, SpaceInst spaceInst) throws AdminException {
        try {
            if (!spaceInst.isRoot()) {
                // It's a subspace
                // Convert the client id in driver id
                int localId = getDriverSpaceId(spaceInst.getDomainFatherId());
                spaceInst.setDomainFatherId(String.valueOf(localId));
                if (useProfileInheritance && !spaceInst.isInheritanceBlocked()) {
                    // inherits profiles from super space
                    // set super space profiles to new space
                    setSpaceProfilesToSubSpace(spaceInst, null);
                }
            }
            // Create the space instance
            spaceInst.setCreatorUserId(userId);
            spaceManager.createSpaceInst(spaceInst);
            // put new space in cache
            cache.opAddSpace(getSpaceInstById(spaceInst.getLocalId()));

            // Instantiate the components
            ArrayList<ComponentInst> alCompoInst = spaceInst.getAllComponentsInst();
            for (ComponentInst componentInst : alCompoInst) {
                componentInst.setDomainFatherId(spaceInst.getId());
                addComponentInst(userId, componentInst);
            }

            SpaceInstLight space = getSpaceInstLight(spaceInst.getLocalId());
            addSpaceInTreeCache(space, true);

            // indexation de l'espace

            createSpaceIndex(space);

            return spaceInst.getId();
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
            try {
                cache.resetCache();
            } catch (Exception e1) {
                SilverLogger.getLogger(this).error(e1);
            }
            throw new AdminException(failureOnAdding(SPACE, spaceInst.getName()), e);
        }
    }

    @Override
    public String deleteSpaceInstById(String userId, String spaceId, boolean definitive) throws AdminException {
        try {
            // Convert the client id in driver id
            int driverSpaceId = getDriverSpaceId(spaceId);

            // Get the space to delete
            SpaceInst spaceInst = getSpaceInstById(driverSpaceId);

            if (!definitive) {
                // Update the space in tables
                spaceManager.sendSpaceToBasket(spaceInst, userId);

                // delete all profiles (space, components and subspaces)
                deleteSpaceProfiles(spaceInst);

                // notify logical deletion
                notifyOnSpaceLogicalDeletion(spaceId);
            } else {
                deleteEffectivelySpaceInst(spaceInst, spaceId, driverSpaceId, userId);
            }

            cache.opRemoveSpace(spaceInst);
            treeCache.removeSpace(driverSpaceId);
            // desindexation de l'espace
            deleteSpaceIndex(spaceInst);
            return spaceId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(SPACE, spaceId), e);
        }
    }

    private void deleteEffectivelySpaceInst(final SpaceInst spaceInst, final String spaceId,
            final int driverSpaceId, final String userId) throws AdminException {
        // Get all the sub-spaces
        String[] subSpaceIds = getAllSubSpaceIds(spaceId);

        // Delete subspaces
        for (String subSpaceid : subSpaceIds) {
            deleteSpaceInstById(userId, subSpaceid, true);
        }

        // Delete subspaces already in bin
        List<SpaceInstLight> removedSpaces = getRemovedSpaces();
        for (SpaceInstLight removedSpace : removedSpaces) {
            if (String.valueOf(driverSpaceId).equals(removedSpace.getFatherId())) {
                deleteSpaceInstById(userId, removedSpace.getId(), true);
            }
        }

        // delete the space profiles instance
        for (int nI = 0; nI < spaceInst.getNumSpaceProfileInst(); nI++) {
            deleteSpaceProfileInst(spaceInst.getSpaceProfileInst(nI).getId());
        }

        // Delete the components
        ArrayList<ComponentInst> alCompoInst = spaceInst.getAllComponentsInst();
        for (ComponentInst anAlCompoInst : alCompoInst) {
            deleteComponentInst(userId, getClientComponentId(anAlCompoInst), true);
        }

        // Delete the components already in bin
        List<ComponentInstLight> removedComponents = getRemovedComponents();
        for (ComponentInstLight removedComponent : removedComponents) {
            if (spaceId.equals(removedComponent.getDomainFatherId())) {
                deleteComponentInst(userId, removedComponent.getId(), true);
            }
        }
        // Delete the space in tables
        spaceManager.deleteSpaceInst(spaceInst);
    }

    private void notifyOnSpaceLogicalDeletion(String spaceId) throws AdminException {
        // notify of space logical deletion
        SpaceInst spaceInst = getSpaceInstById(spaceId);
        spaceEventNotifier.notifyEventOn(ResourceEvent.Type.REMOVING, spaceInst, spaceInst);

        // notify of direct sub spaces logical deletion too
        List<SpaceInstLight> spaces = treeCache.getSubSpaces(getDriverSpaceId(spaceId));
        for (SpaceInstLight space : spaces) {
            notifyOnSpaceLogicalDeletion(space.getId());
        }
    }

    private void deleteSpaceProfiles(SpaceInst spaceInst) throws AdminException {
        // delete the space profiles
        for (int nI = 0; nI < spaceInst.getNumSpaceProfileInst(); nI++) {
            deleteSpaceProfileInst(spaceInst.getSpaceProfileInst(nI).getId());
        }

        // delete the components profiles
        List<ComponentInst> components = spaceInst.getAllComponentsInst();
        for (ComponentInst component : components) {
            for (int p = 0; p < component.getNumProfileInst(); p++) {
                if (!component.getProfileInst(p).isInherited()) {
                    deleteProfileInst(component.getProfileInst(p).getId());
                }
            }
        }

        // delete the subspace profiles
        List<SpaceInst> subSpaces = spaceInst.getSubSpaces();
        for (SpaceInst subSpace : subSpaces) {
            deleteSpaceProfiles(subSpace);
        }
    }

    @Override
    public void restoreSpaceFromBasket(String spaceId) throws AdminException {
        try {
            // Convert the client id in driver id
            int driverSpaceId = getDriverSpaceId(spaceId);
            // update data in database
            spaceManager.removeSpaceFromBasket(driverSpaceId);

            // force caches to be refreshed
            cache.removeSpaceInst(driverSpaceId);
            treeCache.removeSpace(driverSpaceId);

            // Get the space and put it in the cache
            SpaceInst spaceInst = getSpaceInstById(driverSpaceId);
            // set superspace profiles to space
            if (useProfileInheritance && !spaceInst.isInheritanceBlocked() && !spaceInst.isRoot()) {
                updateSpaceInheritance(spaceInst, false);
            }
            // indexation de l'espace
            createSpaceIndex(driverSpaceId);
            // reset space and eventually subspace
            cache.opAddSpace(spaceInst);
            addSpaceInTreeCache(getSpaceInstLight(driverSpaceId), true);
        } catch (Exception e) {
            throw new AdminException(failureOnRestoring(SPACE, spaceId), e);
        }
    }

    @Override
    public SpaceInst getSpaceInstById(String spaceId) throws AdminException {
        try {
            return getSpaceInstById(getDriverSpaceId(spaceId));
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SPACE, spaceId), e);
        }
    }

    /**
     * Get the space instance with the given space id
     *
     * @param spaceId client space id
     * @return Space information as SpaceInst object
     */
    private SpaceInst getSpaceInstById(int spaceId) throws AdminException {
        try {
            final SpaceInst spaceInst;
            Optional<SpaceInst> optionalSpaceInst = cache.getSpaceInst(spaceId);
            if (!optionalSpaceInst.isPresent()) {
                // Get space instance
                spaceInst = spaceManager.getSpaceInstById(spaceId);
                if (spaceInst != null) {
                    // Store the spaceInst in cache
                    cache.putSpaceInst(spaceInst);
                }
            } else {
                spaceInst = optionalSpaceInst.get();
            }
            return spaceManager.copy(spaceInst);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SPACE, String.valueOf(spaceId)), e);
        }
    }

    @Override
    public SpaceInst getPersonalSpace(String userId) throws AdminException {
        return spaceManager.getPersonalSpace(userId);
    }

    @Override
    public String[] getAllSubSpaceIds(String domainFatherId) throws AdminException {
        try {
            // get all sub space ids
            String[] asDriverSpaceIds = spaceManager.getAllSubSpaceIds(getDriverSpaceId(domainFatherId));
            // Convert all the driver space ids in client space ids
            asDriverSpaceIds = getClientSpaceIds(asDriverSpaceIds);

            return asDriverSpaceIds;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SUBSPACES_OF_SPACE, domainFatherId), e);
        }
    }

    @Override
    public String updateSpaceInst(SpaceInst spaceInstNew) throws AdminException {
        try {
            SpaceInst oldSpace = getSpaceInstById(spaceInstNew.getId());
            // Update the space in tables
            spaceManager.updateSpaceInst(spaceInstNew);
            if (useProfileInheritance && (oldSpace.isInheritanceBlocked() != spaceInstNew.isInheritanceBlocked())) {
                updateSpaceInheritance(oldSpace, spaceInstNew.isInheritanceBlocked());
            }
            cache.opUpdateSpace(spaceInstNew);
            Optional<SpaceInstLight> spaceInCache = treeCache.getSpaceInstLight(spaceInstNew.getLocalId());
            spaceInCache.ifPresent(s -> s.setInheritanceBlocked(spaceInstNew.isInheritanceBlocked()));
            // Update space in TreeCache
            SpaceInstLight spaceLight = spaceManager.getSpaceInstLightById(getDriverSpaceId(spaceInstNew.getId()));
            spaceLight.setInheritanceBlocked(spaceInstNew.isInheritanceBlocked());
            treeCache.updateSpace(spaceLight);

            // indexation de l'espace

            createSpaceIndex(spaceLight);
            return spaceInstNew.getId();
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(SPACE, spaceInstNew.getId()), e);
        }
    }

    @Override
    public void updateSpaceOrderNum(String spaceId, int orderNum) throws AdminException {
        try {
            int driverSpaceId = getDriverSpaceId(spaceId);
            // Update the space in tables
            spaceManager.updateSpaceOrder(driverSpaceId, orderNum);
            cache.opUpdateSpace(spaceManager.getSpaceInstById(driverSpaceId));

            // Update space order
            Optional<SpaceInstLight> optionalSpace = treeCache.getSpaceInstLight(driverSpaceId);
            // the space is null if it was just deleted while the update of the ranking concerns one of
            // its sibling
            if (optionalSpace.isPresent()) {
                final SpaceInstLight space = optionalSpace.get();
                space.setOrderNum(orderNum);
                if (!space.isRoot()) {
                    // Update brothers sort in TreeCache
                    treeCache.setSubspaces(getDriverSpaceId(space.getFatherId()),
                            getSubSpaces(space.getFatherId()));
                }
            }
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(SPACE, spaceId), e);
        }
    }

    /**
     * Update the inheritance mode between a subSpace and its space. If inheritanceBlocked is true
     * then all inherited space profiles are removed. If inheritanceBlocked is false then all subSpace
     * profiles are removed and space profiles are inherited.
     *
     * @param space
     * @param inheritanceBlocked
     * @throws AdminException
     */
    private void updateSpaceInheritance(SpaceInst space, boolean inheritanceBlocked) throws AdminException {
        try {
            if (inheritanceBlocked) {
                // suppression des droits hrits de l'espace
                List<SpaceProfileInst> inheritedProfiles = space.getInheritedProfiles();
                for (SpaceProfileInst profile : inheritedProfiles) {
                    deleteSpaceProfileInst(profile.getId());
                }
            } else {
                // Hritage des droits de l'espace
                // 1 - suppression des droits spcifiques du sous espace
                List<SpaceProfileInst> profiles = space.getProfiles();
                for (SpaceProfileInst profile : profiles) {
                    if (profile != null && !profile.isManager()) {
                        deleteSpaceProfileInst(profile.getId());
                    }
                }
                if (!space.isRoot()) {
                    // 2 - affectation des droits de l'espace au sous espace
                    setSpaceProfilesToSubSpace(space, null, true, false);
                }
            }
        } catch (AdminException e) {
            throw new AdminException(failureOnUpdate(SPACE, space.getId()), e);
        }
    }

    @Override
    public boolean isSpaceInstExist(String spaceId) throws AdminException {
        try {
            return spaceManager.isSpaceInstExist(getDriverSpaceId(spaceId));
        } catch (AdminException e) {
            throw new AdminException(e.getMessage(), e);
        }
    }

    @Override
    public String[] getAllRootSpaceIds() throws AdminException {
        try {
            String[] driverSpaceIds = spaceManager.getAllRootSpaceIds();
            // Convert all the driver space ids in client space ids
            driverSpaceIds = getClientSpaceIds(driverSpaceIds);
            return driverSpaceIds;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("root spaces", ""), e);
        }
    }

    @Override
    public List<SpaceInstLight> getPathToComponent(String componentId) throws AdminException {
        List<SpaceInstLight> path = new ArrayList<>();
        ComponentInstLight component = getComponentInstLight(componentId);
        if (component != null) {
            String spaceId = component.getDomainFatherId();
            return getPathToSpace(spaceId, true);
        }
        return path;
    }

    @Override
    public List<SpaceInstLight> getPathToSpace(String spaceId, boolean includeTarget) throws AdminException {
        List<SpaceInstLight> path = new ArrayList<>(10);
        SpaceInstLight space = getSpaceInstLight(getDriverSpaceId(spaceId));
        if (space != null) {
            if (includeTarget) {
                path.add(0, space);
            }
            while (space != null && !space.isRoot()) {
                String fatherId = space.getFatherId();
                space = getSpaceInstLight(getDriverSpaceId(fatherId));
                path.add(0, space);
            }
        }
        return path;
    }

    @Override
    public String[] getAllSpaceIds() throws AdminException {
        try {
            String[] driverSpaceIds = spaceManager.getAllSpaceIds();
            // Convert all the driver space ids in client space ids
            driverSpaceIds = getClientSpaceIds(driverSpaceIds);
            return driverSpaceIds;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all spaces", ""), e);
        }
    }

    @Override
    public List<SpaceInstLight> getRemovedSpaces() throws AdminException {
        try {
            return spaceManager.getRemovedSpaces();
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all removed spaces", ""), e);
        }
    }

    @Override
    public List<ComponentInstLight> getRemovedComponents() throws AdminException {
        try {
            return componentManager.getRemovedComponents();
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all removed components", ""), e);
        }
    }

    @Override
    public String[] getSpaceNames(String[] asClientSpaceIds) throws AdminException {
        if (asClientSpaceIds == null) {
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }
        try {
            String[] asSpaceNames = new String[asClientSpaceIds.length];
            for (int nI = 0; nI < asClientSpaceIds.length; nI++) {
                SpaceInstLight spaceInst = getSpaceInstLightById(asClientSpaceIds[nI]);
                asSpaceNames[nI] = spaceInst.getName();
            }
            return asSpaceNames;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("space names of", String.join(", ", asClientSpaceIds)), e);
        }
    }

    // -------------------------------------------------------------------------
    // COMPONENT RELATED FUNCTIONS
    // -------------------------------------------------------------------------

    @Override
    public Map<String, WAComponent> getAllWAComponents() {
        return componentRegistry.getAllWAComponents();
    }

    @Override
    public SilverpeasComponentInstance getComponentInstance(final String componentInstanceIdentifier)
            throws AdminException {
        final SilverpeasComponentInstance instance;
        try {
            Optional<PersonalComponentInstance> personalComponentInstance = PersonalComponentInstance
                    .from(componentInstanceIdentifier);
            if (personalComponentInstance.isPresent()) {
                instance = personalComponentInstance.get();
            } else {
                final SilverpeasComponentInstance componentInstance = getComponentInst(componentInstanceIdentifier);
                instance = "-1".equals(componentInstance.getId()) ? null : componentInstance;
            }
            return instance;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("Silverpeas component", componentInstanceIdentifier), e);
        }
    }

    @Override
    public ComponentInst getComponentInst(String sClientComponentId) throws AdminException {
        try {
            ComponentInst componentInst = getComponentInst(getDriverComponentId(sClientComponentId), null);
            componentInst = checkComponentInstanceById(componentInst, sClientComponentId,
                    nullComponentInstSupplier);
            Objects.requireNonNull(componentInst);
            componentInst.setDomainFatherId(getClientSpaceId(componentInst.getDomainFatherId()));
            return componentInst;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(COMPONENT, sClientComponentId), e);
        }
    }

    @Override
    public ComponentInstLight getComponentInstLight(String componentId) throws AdminException {
        try {
            final int driverComponentId = getDriverComponentId(componentId);
            final ComponentInstLight instance = componentManager.getComponentInstLight(driverComponentId);
            return checkComponentInstanceById(instance, componentId, null);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(COMPONENT, componentId), e);
        }
    }

    private <T extends SilverpeasComponentInstance> T checkComponentInstanceById(final T componentInstance,
            final String componentId, final Supplier<T> nullComponentInstance) {
        if (componentInstance != null) {
            if (componentInstance.getId().equals(componentId) || "-1".equals(componentInstance.getId())
                    || isLong(componentId)) {
                return componentInstance;
            }
            SilverLogger.getLogger(this).error("{0}. Wrong component {1} has been found!!",
                    failureOnGetting(COMPONENT, componentId), componentInstance.getId());
            return nullComponentInstance != null ? nullComponentInstance.get() : null;
        }
        return null;
    }

    private final Supplier<ComponentInst> nullComponentInstSupplier = () -> {
        try {
            return getComponentInst(-1, -1);
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
            return null;
        }
    };

    /**
     * Return the component Inst corresponding to the given ID.
     *
     * @param componentId
     * @param fatherDriverSpaceId
     * @return the component Inst corresponding to the given ID.
     * @throws AdminException
     */
    private ComponentInst getComponentInst(int componentId, Integer fatherDriverSpaceId) throws AdminException {
        try {
            // Get the component instance
            Optional<ComponentInst> optionalInstance = cache.getComponentInst(componentId);
            final ComponentInst componentInst;
            if (!optionalInstance.isPresent()) {
                // Get component instance from database
                componentInst = componentManager.getComponentInst(componentId, fatherDriverSpaceId);
                // Store component instance in cache
                cache.putComponentInst(componentInst);
            } else {
                componentInst = optionalInstance.get();
            }
            return componentManager.copy(componentInst);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(COMPONENT, String.valueOf(componentId)), e);
        }
    }

    @Override
    public List<Parameter> getComponentParameters(String componentId) {
        try {
            return componentManager.getParameters(getDriverComponentId(componentId));
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
            return Collections.emptyList();
        }
    }

    @Override
    public String getComponentParameterValue(String componentId, String parameterName) {
        List<Parameter> parameters = getComponentParameters(componentId);
        for (Parameter parameter : parameters) {
            if (parameter.getName().equalsIgnoreCase(parameterName)) {
                return parameter.getValue();
            }
        }
        return StringUtil.EMPTY;
    }

    @Override
    public void restoreComponentFromBasket(String componentId) throws AdminException {
        try {
            // update data in database
            componentManager.restoreComponentFromBasket(getDriverComponentId(componentId));

            // Get the component and put it in the cache
            ComponentInst componentInst = getComponentInst(componentId);

            if (useProfileInheritance && !componentInst.isInheritanceBlocked()) {
                // inherits profiles from space
                setSpaceProfilesToComponent(componentInst, null);
            }
            cache.opUpdateComponent(componentInst);
            ComponentInstLight component = getComponentInstLight(componentId);
            treeCache.addComponent(component, getDriverSpaceId(component.getDomainFatherId()));
            createComponentIndex(component);
        } catch (Exception e) {
            throw new AdminException(failureOnRestoring(COMPONENT, componentId));
        }
    }

    @Override
    public void createComponentIndex(String componentId) {
        try {
            final SilverpeasComponentInstance componentInstance = getComponentInstance(componentId);
            if (!componentInstance.isPersonal()) {
                createComponentIndex(componentInstance);
            }
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
        }
    }

    @Override
    public void createComponentIndex(SilverpeasComponentInstance instance) {
        if (instance != null) {
            // Index the component
            String componentId = instance.getId();
            FullIndexEntry indexEntry = new FullIndexEntry(INDEX_COMPONENT_SCOPE, "Component", componentId);
            indexEntry.setTitle(instance.getLabel());
            indexEntry.setPreview(instance.getDescription());
            if (instance instanceof ComponentInst) {
                final ComponentInst componentInst = (ComponentInst) instance;
                indexEntry.setCreationUser(componentInst.getCreatorUserId());
                indexEntry.setCreationDate(componentInst.getCreateDate());
                indexEntry.setLastModificationUser(componentInst.getUpdaterUserId());
                indexEntry.setLastModificationDate(componentInst.getUpdateDate());
            } else if (instance instanceof ComponentInstLight) {
                final ComponentInstLight componentInstLight = (ComponentInstLight) instance;
                indexEntry.setCreationUser(Integer.toString(componentInstLight.getCreatedBy()));
                indexEntry.setCreationDate(componentInstLight.getCreateDate());
                indexEntry.setLastModificationUser(String.valueOf(componentInstLight.getUpdatedBy()));
                indexEntry.setLastModificationDate(componentInstLight.getUpdateDate());
            }
            IndexEngineProxy.addIndexEntry(indexEntry);
        }
    }

    /**
     * Delete the index for the specified component.
     *
     * @param componentId
     */
    private void deleteComponentIndex(String componentId) {
        FullIndexEntry indexEntry = new FullIndexEntry(INDEX_COMPONENT_SCOPE, "Component", componentId);
        IndexEngineProxy.removeIndexEntry(indexEntry.getPK());
    }

    private void deleteComponentData(String componentId) {
        // deleting all files associated to this component
        FileRepositoryManager.deleteAbsolutePath(null, componentId, "");

        // deleting index files
        IndexEngineProxy.removeScopedIndexEntries(componentId);
    }

    @Override
    public void deleteAllComponentIndexes() {
        IndexEngineProxy.removeScopedIndexEntries(INDEX_COMPONENT_SCOPE);
    }

    @Override
    public String addComponentInst(String userId, ComponentInst componentInst)
            throws AdminException, QuotaException {
        try (Connection connection = DBUtil.openConnection()) {
            // Get the father space inst
            SpaceInst spaceInstFather = getSpaceInstById(componentInst.getDomainFatherId());

            // Verify the component space quota
            SpaceServiceProvider.getComponentSpaceQuotaService()
                    .verify(ComponentSpaceQuotaKey.from(spaceInstFather));

            // Create the component instance
            componentManager.createComponentInst(componentInst, spaceInstFather.getLocalId());

            // Add the component to the space
            spaceInstFather.addComponentInst(componentInst);

            // Instantiate the component
            String componentName = componentInst.getName();
            String componentId = componentInst.getId();

            ComponentInstancePostConstruction.get(componentName).ifPresent(c -> c.postConstruct(componentId));

            if (isContentManagedComponent(componentName)) {
                // Call the register functions
                contentManager.registerNewContentInstance(connection, componentId, "containerPDC", componentName);
            }

            if (useProfileInheritance && !componentInst.isInheritanceBlocked()) {
                // inherits profiles from space
                setSpaceProfilesToComponent(componentInst, spaceInstFather);
            }

            cache.opAddComponent(componentInst);

            ComponentInstLight component = getComponentInstLight(componentId);
            treeCache.addComponent(component, getDriverSpaceId(spaceInstFather.getId()));

            // indexation du composant
            createComponentIndex(component);

            return componentId;
        } catch (QuotaException e) {
            throw e;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(COMPONENT, componentInst.getName()), e);
        }
    }

    boolean isContentManagedComponent(String componentName) {
        return "questionReply".equals(componentName) || "whitePages".equals(componentName)
                || "kmelia".equals(componentName) || "kmax".equals(componentName) || "survey".equals(componentName)
                || "toolbox".equals(componentName) || "quickinfo".equals(componentName)
                || "almanach".equals(componentName) || "quizz".equals(componentName)
                || "forums".equals(componentName) || "pollingStation".equals(componentName)
                || "bookmark".equals(componentName) || "chat".equals(componentName)
                || "infoLetter".equals(componentName) || "webSites".equals(componentName)
                || "gallery".equals(componentName) || "blog".equals(componentName);
    }

    /**
     * Deletes the given component instance in Silverpeas
     *
     * @param userId the unique identifier of the user requesting the deletion.
     * @param componentId the client identifier of the component instance (for a kmelia instance of id
     * 666, the client identifier of the instance is kmelia666)
     * @param definitive is the component instance deletion is definitive? If not, the component
     * instance is moved into the bin.
     * @throws AdminException if an error occurs while deleting the
     * component instance.
     */
    @Override
    public String deleteComponentInst(String userId, String componentId, boolean definitive) throws AdminException {
        try {
            // Convert the client id in driver id
            int sDriverComponentId = getDriverComponentId(componentId);

            // Get the component to delete
            ComponentInst componentInst = getComponentInst(sDriverComponentId, null);
            componentInst = checkComponentInstanceById(componentInst, componentId, nullComponentInstSupplier);
            Objects.requireNonNull(componentInst);

            // Get the father id
            String sFatherClientId = componentInst.getDomainFatherId();

            // Check if component is used as space homepage
            SpaceInst space = getSpaceInstById(sFatherClientId);
            if (space.getFirstPageType() == SpaceInst.FP_TYPE_COMPONENT_INST
                    && space.getFirstPageExtraParam().equals(componentId)) {
                space.setFirstPageType(SpaceInst.FP_TYPE_STANDARD);
                space.setFirstPageExtraParam(null);
                updateSpaceInst(space);
            }

            if (!definitive) {
                // delete the profiles instance
                for (int nI = 0; nI < componentInst.getNumProfileInst(); nI++) {
                    deleteProfileInst(componentInst.getProfileInst(nI).getId());
                }
                componentManager.sendComponentToBasket(componentInst, userId);
            } else {
                try (Connection connection = DBUtil.openConnection()) {
                    // Uninstantiate the components
                    String componentName = componentInst.getName();

                    ComponentInstancePreDestruction.get(componentName).ifPresent(c -> c.preDestroy(componentId));

                    ServiceProvider.getAllServices(ComponentInstanceDeletion.class).stream()
                            .forEach(service -> service.delete(componentId));

                    // delete the profiles instance
                    for (int nI = 0; nI < componentInst.getNumProfileInst(); nI++) {
                        deleteProfileInst(componentInst.getProfileInst(nI).getId());
                    }

                    if (isContentManagedComponent(componentName)) {
                        // Call the unregister functions
                        contentManager.unregisterNewContentInstance(connection, componentId, "containerPDC",
                                componentName);
                    }
                }
                // Delete the component
                componentManager.deleteComponentInst(componentInst);
            }

            cache.opRemoveComponent(componentInst);
            treeCache.removeComponent(getDriverSpaceId(sFatherClientId), componentId);

            // unindex component
            deleteComponentIndex(componentId);

            if (definitive) {
                // delete definitively data stored on file server
                deleteComponentData(componentId);
            }

            return componentId;
        } catch (SQLException | ContentManagerException e) {
            throw new AdminException(failureOnDeleting(COMPONENT, componentId), e);
        }
    }

    @Override
    public void updateComponentOrderNum(String componentId, int orderNum) throws AdminException {
        try {
            int driverComponentId = getDriverComponentId(componentId);
            // Update the Component in tables
            componentManager.updateComponentOrder(driverComponentId, orderNum);
            cache.opUpdateComponent(componentManager.getComponentInst(driverComponentId, null));
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(COMPONENT, componentId), e);
        }
    }

    @Override
    public String updateComponentInst(ComponentInst component) throws AdminException {
        try {
            ComponentInst oldComponent = getComponentInst(component.getId());
            String componentClientId = getClientComponentId(oldComponent);

            // Update the components in tables
            componentManager.updateComponentInst(oldComponent, component);

            // Update the inherited rights
            if (useProfileInheritance
                    && (oldComponent.isInheritanceBlocked() != component.isInheritanceBlocked())) {
                updateComponentInheritance(oldComponent, component.isInheritanceBlocked());
            }

            cache.opUpdateComponent(component);
            treeCache.updateComponent(getComponentInstLight(component.getId()));

            // indexation du composant
            createComponentIndex(componentClientId);

            return componentClientId;
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(COMPONENT, component.getId()), e);
        }
    }

    /**
     * Update the inheritance mode between a component and its space. If inheritanceBlocked is true
     * then all inherited space profiles are removed. If inheritanceBlocked is false then all
     * component profiles are removed and space profiles are inherited.
     *
     * @param component
     * @param inheritanceBlocked
     * @throws AdminException
     */
    private void updateComponentInheritance(ComponentInst component, boolean inheritanceBlocked)
            throws AdminException {
        try {
            if (inheritanceBlocked) {
                // suppression des droits hrits de l'espace
                List<ProfileInst> inheritedProfiles = component.getInheritedProfiles();
                for (ProfileInst profile : inheritedProfiles) {
                    deleteProfileInst(profile.getId());
                }
            } else {
                // suppression des droits du composant
                List<ProfileInst> profiles = component.getProfiles();
                for (ProfileInst profile : profiles) {
                    deleteProfileInst(profile.getId());
                }
                // affectation des droits de l'espace
                setSpaceProfilesToComponent(component, null);
            }
        } catch (AdminException e) {
            throw new AdminException(failureOnUpdate(COMPONENT, component.getId()), e);
        }
    }

    @Override
    public void setSpaceProfilesToSubSpace(final SpaceInst subSpace, final SpaceInst space) throws AdminException {
        setSpaceProfilesToSubSpace(subSpace, space, false, false);
    }

    @Override
    public void setSpaceProfilesToSubSpace(final SpaceInst subSpace, final SpaceInst space, boolean persist,
            boolean startNewTransaction) throws AdminException {
        SpaceInst currentSpace = space;
        if (currentSpace == null) {
            currentSpace = getSpaceInstById(subSpace.getDomainFatherId());
        }

        setSpaceProfileToSubSpace(subSpace, currentSpace, SilverpeasRole.admin);
        setSpaceProfileToSubSpace(subSpace, currentSpace, SilverpeasRole.publisher);
        setSpaceProfileToSubSpace(subSpace, currentSpace, SilverpeasRole.writer);
        setSpaceProfileToSubSpace(subSpace, currentSpace, SilverpeasRole.reader);

        if (persist) {
            for (SpaceProfileInst profile : subSpace.getInheritedProfiles()) {
                if (StringUtil.isDefined(profile.getId())) {
                    if (profile.isEmpty()) {
                        // we delete a space profile in this context only if it is empty
                        deleteSpaceProfileInst(profile.getId(), null);
                    } else {
                        updateSpaceProfileInst(profile, null);
                    }
                } else {
                    addSpaceProfileInst(profile, null);
                }
            }
        }
    }

    /**
     * Set space profile to a subspace. There is no persistance. The subspace object is enriched.
     *
     * @param subSpace the object to set profiles
     * @param space the object to get profiles
     * @param role the name of the profile
     * @throws AdminException
     */
    private void setSpaceProfileToSubSpace(SpaceInst subSpace, SpaceInst space, SilverpeasRole role) {
        String profileName = role.toString();
        SpaceProfileInst subSpaceProfile = subSpace.getInheritedSpaceProfileInst(profileName);
        if (subSpaceProfile != null) {
            subSpaceProfile.removeAllGroups();
            subSpaceProfile.removeAllUsers();
        } else {
            subSpaceProfile = new SpaceProfileInst();
            subSpaceProfile.setName(profileName);
            subSpaceProfile.setInherited(true);
        }

        // Retrieve superSpace local profile
        SpaceProfileInst profile = space.getSpaceProfileInst(profileName);
        if (profile != null) {
            subSpaceProfile.addGroups(profile.getAllGroups());
            subSpaceProfile.addUsers(profile.getAllUsers());
        }

        // Retrieve superSpace inherited profile
        SpaceProfileInst inheritedProfile = space.getInheritedSpaceProfileInst(profileName);
        if (inheritedProfile != null) {
            subSpaceProfile.addGroups(inheritedProfile.getAllGroups());
            subSpaceProfile.addUsers(inheritedProfile.getAllUsers());
        }

        if (!subSpaceProfile.isEmpty()) {
            subSpace.addSpaceProfileInst(subSpaceProfile);
        }
    }

    @Override
    public void setSpaceProfilesToComponent(final ComponentInst component, final SpaceInst spaceInst)
            throws AdminException {
        WAComponent waComponent = componentRegistry.getWAComponent(component.getName())
                .orElseThrow(() -> new AdminException("No such component with name " + component.getName()));
        List<Profile> componentRoles = waComponent.getProfiles();
        final SpaceInst space = spaceInst == null ? getSpaceInstById(component.getDomainFatherId()) : spaceInst;
        try {
            for (Profile componentRole : componentRoles) {
                ProfileInst inheritedProfile = removeInheritedComponentRole(component, componentRole);
                List<String> spaceRoles = componentRole2SpaceRoles(componentRole.getName(), component.getName());
                removeInheritedSpaceRole(space, spaceRoles, inheritedProfile);
                if (StringUtil.isDefined(inheritedProfile.getId())) {
                    updateProfileInst(inheritedProfile);
                } else {
                    if (!inheritedProfile.isEmpty()) {
                        addProfileInst(inheritedProfile, null);
                    }
                }
            }
        } catch (Exception e) {
            throw new AdminException(
                    "Fail to set profiles of space " + space.getId() + " to component" + component.getId(), e);
        }

        // Now that rights are applied on component, check rights on component objects to delete
        // groups or users who have no more rights on component
        checkObjectsProfiles(component.getId());
    }

    private void removeInheritedSpaceRole(final SpaceInst space, final List<String> spaceRoles,
            final ProfileInst inheritedProfile) {
        for (final String spaceRole : spaceRoles) {
            SpaceProfileInst spaceProfile = space.getSpaceProfileInst(spaceRole);
            if (spaceProfile != null) {
                inheritedProfile.addGroups(spaceProfile.getAllGroups());
                inheritedProfile.addUsers(spaceProfile.getAllUsers());
            }

            spaceProfile = space.getInheritedSpaceProfileInst(spaceRole);
            if (spaceProfile != null) {
                inheritedProfile.addGroups(spaceProfile.getAllGroups());
                inheritedProfile.addUsers(spaceProfile.getAllUsers());
            }
        }
    }

    @NotNull
    private ProfileInst removeInheritedComponentRole(final ComponentInst component, final Profile componentRole) {
        ProfileInst inheritedProfile = component.getInheritedProfileInst(componentRole.getName());
        if (inheritedProfile != null) {
            inheritedProfile.removeAllGroups();
            inheritedProfile.removeAllUsers();
        } else {
            inheritedProfile = new ProfileInst();
            inheritedProfile.setComponentFatherId(component.getId());
            inheritedProfile.setInherited(true);
            inheritedProfile.setName(componentRole.getName());
        }
        return inheritedProfile;
    }

    private void checkObjectsProfiles(String componentId) {
        List<ProfileInst> objectsProfiles = getProfileInsts(componentId);
        for (ProfileInst objectProfile : objectsProfiles) {
            try {
                List<String> groupIdsToRemove = getGroupIdsToRemove(componentId, objectProfile);
                List<String> userIdsToRemove = getUserIdsToRemove(componentId, objectProfile);
                if (!groupIdsToRemove.isEmpty() || !userIdsToRemove.isEmpty()) {
                    for (String groupId : groupIdsToRemove) {
                        objectProfile.removeGroup(groupId);
                    }
                    for (String userId : userIdsToRemove) {
                        objectProfile.removeUser(userId);
                    }
                    profileManager.updateProfileInst(objectProfile);
                }
            } catch (Exception e) {
                SilverLogger.getLogger(this).warn("Error when checking object profile " + objectProfile.getId(), e);
            }
        }
    }

    @NotNull
    private List<String> getUserIdsToRemove(final String componentId, final ProfileInst objectProfile)
            throws AdminException {
        List<String> userIdsToRemove = new ArrayList<>();
        List<String> userIds = objectProfile.getAllUsers();
        for (String userId : userIds) {
            if (!isComponentAvailable(componentId, userId)) {
                userIdsToRemove.add(userId);
            }
        }
        return userIdsToRemove;
    }

    @NotNull
    private List<String> getGroupIdsToRemove(final String componentId, final ProfileInst objectProfile)
            throws AdminException {
        List<String> groupIdsToRemove = new ArrayList<>();
        List<String> groupIds = objectProfile.getAllGroups();
        for (String groupId : groupIds) {
            if (!isComponentAvailableByGroup(componentId, groupId)) {
                groupIdsToRemove.add(groupId);
            }
        }
        return groupIdsToRemove;
    }

    @Nullable
    private List<ProfileInst> getProfileInsts(final String componentId) {
        List<ProfileInst> objectsProfiles = null;
        try {
            int shortComponentId = getDriverComponentId(componentId);
            objectsProfiles = profiledObjectManager.getProfiles(shortComponentId);
        } catch (Exception e) {
            SilverLogger.getLogger(this).warn("Error when getting all component objects profiles " + componentId,
                    e);
        }
        return objectsProfiles;
    }

    private boolean isComponentAvailableByGroup(String componentId, String groupId) throws AdminException {
        List<String> groupIds = groupManager.getPathToGroup(groupId);
        groupIds.add(groupId);
        return componentManager.getAllowedComponentIds(-1, groupIds).contains(componentId);
    }

    @Override
    public void moveSpace(String spaceId, String fatherId) throws AdminException {
        if (isParent(getDriverSpaceId(spaceId), getDriverSpaceId(fatherId))) {
            // space cannot be moved in one of its descendants
            return;
        }

        int shortSpaceId = getDriverSpaceId(spaceId);
        int shortFatherId = StringUtil.isDefined(fatherId) ? getDriverSpaceId(fatherId) : -1;
        boolean moveOnTop = shortFatherId == -1;

        SpaceInst space = getSpaceInstById(shortSpaceId);
        int shortOldSpaceId = getDriverSpaceId(space.getDomainFatherId());

        // move space in database
        spaceManager.moveSpace(shortSpaceId, shortFatherId);

        // set space in last rank
        spaceManager.updateSpaceOrder(shortSpaceId, getAllSubSpaceIds(fatherId).length);

        if (useProfileInheritance) {
            processProfileInstsOnSpaceMove(shortSpaceId, shortFatherId, moveOnTop);
        }

        // reset caches
        cache.resetSpaceInst();
        treeCache.removeSpace(shortSpaceId);
        treeCache.setSubspaces(shortOldSpaceId, spaceManager.getSubSpaces(shortOldSpaceId));
        addSpaceInTreeCache(spaceManager.getSpaceInstLightById(shortSpaceId), false);
        if (!moveOnTop) {
            treeCache.setSubspaces(shortFatherId, spaceManager.getSubSpaces(shortFatherId));
        }

        String[] allComponentIds = getAllComponentIdsRecur(spaceId);
        for (String componentId : allComponentIds) {
            checkObjectsProfiles(componentId);
        }
    }

    private void processProfileInstsOnSpaceMove(final int shortSpaceId, final int shortFatherId,
            final boolean moveOnTop) throws AdminException {
        final SpaceInst space;
        space = spaceManager.getSpaceInstById(shortSpaceId);

        if (moveOnTop) {
            // inherited rights must be removed but local rights are preserved
            List<SpaceProfileInst> inheritedProfiles = space.getInheritedProfiles();
            for (SpaceProfileInst profile : inheritedProfiles) {
                deleteSpaceProfileInst(profile.getId());
            }
        } else {
            if (!space.isInheritanceBlocked()) {
                // space inherits rights from parent
                SpaceInst father = getSpaceInstById(shortFatherId);
                setSpaceProfilesToSubSpace(space, father, true, false);
            } else {
                // space uses only local rights
                // let it as it is
            }
        }

        // Merge inherited and specific for each type of profile
        Map<String, SpaceProfileInst> mergedProfiles = new HashMap<>();
        List<SpaceProfileInst> allProfiles = new ArrayList<>();
        allProfiles.addAll(space.getProfiles());
        if (!moveOnTop) {
            allProfiles.addAll(space.getInheritedProfiles());
        }
        for (SpaceProfileInst profile : allProfiles) {
            SpaceProfileInst mergedProfile = mergedProfiles.get(profile.getName());
            if (mergedProfile == null) {
                mergedProfile = new SpaceProfileInst();
                mergedProfile.setName(profile.getName());
                mergedProfile.setInherited(true);
                mergedProfiles.put(profile.getName(), mergedProfile);
            }
            mergedProfile.addGroups(profile.getAllGroups());
            mergedProfile.addUsers(profile.getAllUsers());
        }

        // Spread profiles
        for (SpaceProfileInst profile : mergedProfiles.values()) {
            spreadSpaceProfile(shortSpaceId, profile);
        }

        if (moveOnTop) {
            // on top level, space inheritance is not applicable
            space.setInheritanceBlocked(false);
            spaceManager.updateSpaceInst(space);
        }
    }

    @Override
    public void moveComponentInst(String spaceId, String componentId, String idComponentBefore,
            ComponentInst[] componentInsts) throws AdminException {
        try {

            int sDriverComponentId = getDriverComponentId(componentId);
            // Convert the client space Id in driver space Id
            int sDriverSpaceId = getDriverSpaceId(spaceId);

            ComponentInst componentInst = getComponentInst(componentId);
            String oldSpaceId = componentInst.getDomainFatherId();
            // Update the components in tables
            componentManager.moveComponentInst(sDriverSpaceId, sDriverComponentId);
            componentInst.setDomainFatherId(String.valueOf(sDriverSpaceId));

            // set space profiles to component if it not use its own rights
            if (!componentInst.isInheritanceBlocked()) {
                setSpaceProfilesToComponent(componentInst, null);
            }

            if (StringUtil.isDefined(idComponentBefore) && componentInsts != null) {
                // Set component in order
                setComponentPlace(componentId, idComponentBefore, componentInsts);
            } else {
                // set component in last rank
                updateComponentOrderNum(componentId, getAllComponentIds(spaceId).length);
            }

            // Update extraParamPage from Space if necessary
            SpaceInst fromSpace = getSpaceInstById(getDriverSpaceId(oldSpaceId));
            String spaceHomePage = fromSpace.getFirstPageExtraParam();

            if (StringUtil.isDefined(spaceHomePage) && spaceHomePage.equals(componentId)) {
                fromSpace.setFirstPageExtraParam("");
                fromSpace.setFirstPageType(0);
                updateSpaceInst(fromSpace);
            }
            // Remove component from the Cache
            cache.resetSpaceInst();
            cache.resetComponentInst();
            treeCache.setComponents(getDriverSpaceId(oldSpaceId),
                    componentManager.getComponentsInSpace(getDriverSpaceId(oldSpaceId)));
            treeCache.setComponents(getDriverSpaceId(spaceId),
                    componentManager.getComponentsInSpace(getDriverSpaceId(spaceId)));
        } catch (Exception e) {
            throw new AdminException("Fail to move component " + componentId + " into space " + spaceId, e);
        }
    }

    @Override
    public void setComponentPlace(String componentId, String idComponentBefore, ComponentInst[] brothersComponents)
            throws AdminException {
        int orderNum = 0;
        int i;
        ComponentInst theComponent = getComponentInst(componentId);

        for (i = 0; i < brothersComponents.length; i++) {
            if (idComponentBefore.equals(brothersComponents[i].getId())) {
                theComponent.setOrderNum(orderNum);
                updateComponentOrderNum(theComponent.getId(), orderNum);
                orderNum++;
            }
            if (brothersComponents[i].getOrderNum() != orderNum) {
                brothersComponents[i].setOrderNum(orderNum);
                updateComponentOrderNum(brothersComponents[i].getId(), orderNum);
            }
            orderNum++;
        }
        if (orderNum == i) {
            theComponent.setOrderNum(orderNum);
            updateComponentOrderNum(theComponent.getId(), orderNum);
        }
    }

    @Override
    public String getRequestRouter(String sComponentName) {
        return componentRegistry.getWAComponent(sComponentName).filter(wac -> StringUtil.isDefined(wac.getRouter()))
                .map(WAComponent::getRouter).orElse("R" + sComponentName);
    }

    // --------------------------------------------------------------------------------------------------------
    // PROFILE RELATED FUNCTIONS
    // --------------------------------------------------------------------------------------------------------
    @Override
    public String[] getAllProfilesNames(String sComponentName) {
        List<String> asProfiles = new ArrayList<>();
        componentRegistry.getWAComponent(sComponentName).ifPresent(wac -> {
            List<Profile> profiles = wac.getProfiles();
            List<String> profileNames = new ArrayList<>(profiles.size());
            for (Profile profile : profiles) {
                profileNames.add(profile.getName());
            }
            asProfiles.addAll(profileNames);
        });
        return asProfiles.toArray(new String[asProfiles.size()]);
    }

    @Override
    public String getProfileLabelfromName(String sComponentName, String sProfileName, String lang) {
        return componentRegistry.getWAComponent(sComponentName).map(wac -> {
            List<Profile> profiles = wac.getProfiles();
            for (Profile profile : profiles) {
                if (profile.getName().equals(sProfileName)) {
                    return profile.getLabel().get(lang);
                }
            }
            return sProfileName;
        }).orElse(sProfileName);
    }

    @Override
    public ProfileInst getProfileInst(String sProfileId) throws AdminException {
        final ProfileInst profileInst;
        Optional<ProfileInst> optionalProfile = cache.getProfileInst(sProfileId);
        if (!optionalProfile.isPresent()) {
            profileInst = profileManager.getProfileInst(sProfileId);
            cache.putProfileInst(profileInst);
        } else {
            profileInst = optionalProfile.get();
        }
        return profileInst;
    }

    @Override
    public List<ProfileInst> getProfilesByObject(String objectId, String objectType, String componentId)
            throws AdminException {
        return profiledObjectManager.getProfiles(Integer.parseInt(objectId), objectType,
                getDriverComponentId(componentId));
    }

    @Override
    public String[] getProfilesByObjectAndUserId(int objectId, String objectType, String componentId, String userId)
            throws AdminException {
        List<String> groups = getAllGroupsOfUser(userId);
        return profiledObjectManager.getUserProfileNames(objectId, objectType, getDriverComponentId(componentId),
                Integer.parseInt(userId), groups);
    }

    @Override
    public Map<Integer, List<String>> getProfilesByObjectTypeAndUserId(String objectType, String componentId,
            String userId) throws AdminException {
        List<String> groups = getAllGroupsOfUser(userId);
        return profiledObjectManager.getUserProfileNames(objectType, getDriverComponentId(componentId),
                Integer.parseInt(userId), groups);
    }

    @Override
    public boolean isObjectAvailable(String componentId, int objectId, String objectType, String userId)
            throws AdminException {
        return userId == null || getProfilesByObjectAndUserId(objectId, objectType, componentId, userId).length > 0;
    }

    @Override
    public String addProfileInst(ProfileInst profileInst) throws AdminException {
        return addProfileInst(profileInst, null);
    }

    /**
     * Get the given profile instance from Silverpeas
     */
    @Override
    public String addProfileInst(ProfileInst profileInst, String userId) throws AdminException {
        try {
            final String componentFatherId = profileInst.getComponentFatherId();
            int driverFatherId = getDriverComponentId(componentFatherId);
            String sProfileId = profileManager.createProfileInst(profileInst, driverFatherId);
            profileInst.setId(sProfileId);

            if (profileInst.getObjectId() == -1 || profileInst.getObjectId() == 0) {
                ComponentInst componentInstFather = getComponentInst(driverFatherId, null);
                componentInstFather = checkComponentInstanceById(componentInstFather, componentFatherId,
                        nullComponentInstSupplier);
                Objects.requireNonNull(componentInstFather);
                componentInstFather.addProfileInst(profileInst);
                if (StringUtil.isDefined(userId)) {
                    componentInstFather.setUpdaterUserId(userId);
                    updateComponentInst(componentInstFather);
                }
            }

            if (profileInst.getObjectId() == -1 || profileInst.getObjectId() == 0) {
                cache.opAddProfile(profileManager.getProfileInst(sProfileId));
            }
            return sProfileId;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(PROFILE, profileInst.getName()), e);
        }
    }

    private String deleteProfileInst(String sProfileId) throws AdminException {
        return deleteProfileInst(sProfileId, null);
    }

    /**
     * Delete the given profile from Silverpeas
     *
     * @param profileId
     * @param userId
     * @return
     * @throws AdminException
     */
    @Override
    public String deleteProfileInst(String profileId, String userId) throws AdminException {
        ProfileInst profile = profileManager.getProfileInst(profileId);
        try {
            profileManager.deleteProfileInst(profile);
            if (StringUtil.isDefined(userId) && (profile.getObjectId() == -1 || profile.getObjectId() == 0)) {
                final String componentFatherId = profile.getComponentFatherId();
                int driverFatherId = getDriverComponentId(componentFatherId);
                ComponentInst component = getComponentInst(driverFatherId, null);
                component = checkComponentInstanceById(component, componentFatherId, nullComponentInstSupplier);
                Objects.requireNonNull(component);

                component.setUpdaterUserId(userId);
                updateComponentInst(component);
            }

            if (profile.getObjectId() == -1 || profile.getObjectId() == 0) {
                cache.opRemoveProfile(profile);
            }

            return profileId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(PROFILE, profileId), e);
        }
    }

    @Override
    public String updateProfileInst(ProfileInst profileInstNew) throws AdminException {
        return doUpdateProfileInst(profileInstNew, null);
    }

    @Override
    public String updateProfileInst(ProfileInst profileInstNew, String userId) throws AdminException {
        return doUpdateProfileInst(profileInstNew, userId);
    }

    private String doUpdateProfileInst(ProfileInst newProfile, String userId) throws AdminException {
        try {
            profileManager.updateProfileInst(newProfile);
            if (StringUtil.isDefined(userId) && (newProfile.getObjectId() == -1 || newProfile.getObjectId() == 0)) {
                final String componentFatherId = newProfile.getComponentFatherId();
                int driverFatherId = getDriverComponentId(componentFatherId);
                ComponentInst component = getComponentInst(driverFatherId, null);
                component = checkComponentInstanceById(component, componentFatherId, nullComponentInstSupplier);
                Objects.requireNonNull(component);
                component.setUpdaterUserId(userId);
                updateComponentInst(component);
            }
            if (newProfile.getObjectId() == -1 || newProfile.getObjectId() == 0) {
                cache.opUpdateProfile(newProfile);
            }

            return newProfile.getId();
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(PROFILE, newProfile.getId()), e);
        }
    }

    // --------------------------------------------------------------------------------------------------------
    // SPACE PROFILE RELATED FUNCTIONS
    // --------------------------------------------------------------------------------------------------------
    @Override
    public SpaceProfileInst getSpaceProfileInst(String spaceProfileId) throws AdminException {
        return spaceProfileManager.getSpaceProfileInst(spaceProfileId);
    }

    /**
     * Add the space profile instance from Silverpeas.
     *
     * @param spaceProfile
     * @param userId
     * @return
     * @throws AdminException
     */
    @Override
    public String addSpaceProfileInst(SpaceProfileInst spaceProfile, String userId) throws AdminException {
        try {
            Integer spaceId = getDriverComponentId(spaceProfile.getSpaceFatherId());
            String sSpaceProfileId = spaceProfileManager.createSpaceProfileInst(spaceProfile, spaceId);
            spaceProfile.setId(sSpaceProfileId);
            if (StringUtil.isDefined(userId)) {
                SpaceInst spaceInstFather = getSpaceInstById(spaceId);
                spaceInstFather.setUpdaterUserId(userId);
                updateSpaceInst(spaceInstFather);
            }
            // add new profile in spaces cache
            Optional<SpaceInst> spaceInst = cache.getSpaceInst(spaceId);
            spaceInst.ifPresent(s -> s.addSpaceProfileInst(spaceProfile));

            // profile 'Manager' does not need to be spread
            if (!spaceProfile.isManager()) {
                spreadInheritedSpaceProfile(spaceProfile, spaceId);
            }

            cache.opAddSpaceProfile(spaceProfile);
            return sSpaceProfileId;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(SPACE_PROFILE, spaceProfile.getName()), e);
        }
    }

    private void spreadInheritedSpaceProfile(final SpaceProfileInst spaceProfile, final Integer spaceId)
            throws AdminException {
        if (!spaceProfile.isInherited()) {
            SpaceProfileInst inheritedProfile = spaceProfileManager.getInheritedSpaceProfileInstByName(spaceId,
                    spaceProfile.getName());
            if (inheritedProfile != null) {
                spaceProfile.addGroups(inheritedProfile.getAllGroups());
                spaceProfile.addUsers(inheritedProfile.getAllUsers());
            }
        }
        spreadSpaceProfile(spaceId, spaceProfile);
    }

    private String deleteSpaceProfileInst(String sSpaceProfileId) throws AdminException {
        return deleteSpaceProfileInst(sSpaceProfileId, null);
    }

    /**
     * Delete the given space profile from Silverpeas
     */
    @Override
    public String deleteSpaceProfileInst(String sSpaceProfileId, String userId) throws AdminException {
        SpaceProfileInst spaceProfileInst = spaceProfileManager.getSpaceProfileInst(sSpaceProfileId);
        if (spaceProfileInst == null) {
            return sSpaceProfileId;
        }
        try {
            spaceProfileManager.deleteSpaceProfileInst(spaceProfileInst);
            cache.opRemoveSpaceProfile(spaceProfileInst);
            spaceProfileInst.removeAllGroups();
            spaceProfileInst.removeAllUsers();
            Integer spaceId = getDriverComponentId(spaceProfileInst.getSpaceFatherId());
            if (StringUtil.isDefined(userId)) {
                SpaceInst spaceInstFather = getSpaceInstById(spaceId);
                spaceInstFather.setUpdaterUserId(userId);
                updateSpaceInst(spaceInstFather);
            }
            spreadInheritedSpaceProfile(spaceProfileInst, spaceId);

            return sSpaceProfileId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(SPACE_PROFILE, sSpaceProfileId), e);
        }
    }

    /**
     * Update the given space profile in Silverpeas
     */
    private String updateSpaceProfileInst(SpaceProfileInst newSpaceProfile) throws AdminException {
        return updateSpaceProfileInst(newSpaceProfile, null);
    }

    @Override
    public String updateSpaceProfileInst(SpaceProfileInst newSpaceProfile, String userId) throws AdminException {
        try {
            SpaceProfileInst oldSpaceProfile = spaceProfileManager.getSpaceProfileInst(newSpaceProfile.getId());
            if (oldSpaceProfile == null) {
                return null;
            }
            String spaceProfileNewId = spaceProfileManager.updateSpaceProfileInst(oldSpaceProfile, newSpaceProfile);

            // profile 'Manager' does not need to be spread
            if (!oldSpaceProfile.isManager()) {
                int spaceId = getDriverSpaceId(newSpaceProfile.getSpaceFatherId());
                if (StringUtil.isDefined(userId)) {
                    SpaceInst spaceInstFather = getSpaceInstById(spaceId);
                    spaceInstFather.setUpdaterUserId(userId);
                    updateSpaceInst(spaceInstFather);
                }
                // Add inherited users and groups for this role
                List<SpaceProfileInst> allProfileSources = new ArrayList<>();
                allProfileSources.add(newSpaceProfile);
                if (newSpaceProfile.isInherited()) {
                    allProfileSources
                            .add(spaceProfileManager.getSpaceProfileInstByName(spaceId, oldSpaceProfile.getName()));
                } else {
                    allProfileSources.add(spaceProfileManager.getInheritedSpaceProfileInstByName(spaceId,
                            oldSpaceProfile.getName()));
                }
                SpaceProfileInst profileToSpread = new SpaceProfileInst();
                profileToSpread.setName(oldSpaceProfile.getName());
                profileToSpread.setInherited(true);
                allProfileSources.remove(null);
                for (SpaceProfileInst spaceProfile : allProfileSources) {
                    profileToSpread.addGroups(spaceProfile.getAllGroups());
                    profileToSpread.addUsers(spaceProfile.getAllUsers());
                }
                spreadSpaceProfile(spaceId, profileToSpread);
            }
            cache.opUpdateSpaceProfile(spaceProfileManager.getSpaceProfileInst(newSpaceProfile.getId()));

            return spaceProfileNewId;
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(SPACE_PROFILE, newSpaceProfile.getId()), e);
        }
    }

    private String spaceRole2ComponentRole(String spaceRole, String componentName) {
        return roleMapping.getString(componentName + "_" + spaceRole, null);
    }

    private List<String> componentRole2SpaceRoles(String componentRole, String componentName) {
        List<String> roles = new ArrayList<>();

        String role = spaceRole2ComponentRole(SilverpeasRole.admin.toString(), componentName);
        if (role != null && role.equalsIgnoreCase(componentRole)) {
            roles.add(SilverpeasRole.admin.toString());
        }
        role = spaceRole2ComponentRole(SilverpeasRole.publisher.toString(), componentName);
        if (role != null && role.equalsIgnoreCase(componentRole)) {
            roles.add(SilverpeasRole.publisher.toString());
        }
        role = spaceRole2ComponentRole(SilverpeasRole.writer.toString(), componentName);
        if (role != null && role.equalsIgnoreCase(componentRole)) {
            roles.add(SilverpeasRole.writer.toString());
        }
        role = spaceRole2ComponentRole(SilverpeasRole.reader.toString(), componentName);
        if (role != null && role.equalsIgnoreCase(componentRole)) {
            roles.add(SilverpeasRole.reader.toString());
        }
        return roles;
    }

    private void spreadSpaceProfile(int spaceId, SpaceProfileInst spaceProfile) throws AdminException {
        // update profile in components
        List<ComponentInstLight> components = treeCache.getComponents(spaceId);
        updateProfilesInComponents(spaceId, spaceProfile, components);

        // update profile in subspaces
        List<SpaceInstLight> subSpaces = treeCache.getSubSpaces(spaceId);
        updateProfilesInSubspaces(spaceProfile, subSpaces);
    }

    private void updateProfilesInSubspaces(final SpaceProfileInst spaceProfile,
            final List<SpaceInstLight> subSpaces) throws AdminException {
        for (SpaceInstLight subSpace : subSpaces) {
            if (!subSpace.isInheritanceBlocked()) {
                SpaceProfileInst subSpaceProfile = spaceProfileManager
                        .getInheritedSpaceProfileInstByName(subSpace.getLocalId(), spaceProfile.getName());
                if (subSpaceProfile != null) {
                    subSpaceProfile.setGroups(spaceProfile.getAllGroups());
                    subSpaceProfile.setUsers(spaceProfile.getAllUsers());
                    updateSpaceProfileInst(subSpaceProfile);
                } else {
                    subSpaceProfile = new SpaceProfileInst();
                    subSpaceProfile.setName(spaceProfile.getName());
                    subSpaceProfile.setInherited(true);
                    subSpaceProfile.setSpaceFatherId(String.valueOf(subSpace.getLocalId()));
                    subSpaceProfile.addGroups(spaceProfile.getAllGroups());
                    subSpaceProfile.addUsers(spaceProfile.getAllUsers());
                    if (!subSpaceProfile.getAllGroups().isEmpty() || !subSpaceProfile.getAllUsers().isEmpty()) {
                        addSpaceProfileInst(subSpaceProfile, null);
                    }
                }
            }
        }
    }

    private void updateProfilesInComponents(final int spaceId, final SpaceProfileInst spaceProfile,
            final List<ComponentInstLight> components) throws AdminException {
        for (ComponentInstLight component : components) {
            if (component != null && !component.isInheritanceBlocked()) {
                String componentRole = spaceRole2ComponentRole(spaceProfile.getName(), component.getName());
                if (componentRole != null) {
                    ProfileInst inheritedProfile = profileManager.getInheritedProfileInst(component.getLocalId(),
                            componentRole);
                    if (inheritedProfile != null) {
                        updateInheritedProfileInsts(spaceId, spaceProfile, inheritedProfile, componentRole,
                                component);
                    } else {
                        addNewProfileInsts(spaceProfile, componentRole, component);
                    }
                }
            }
        }
    }

    private void addNewProfileInsts(final SpaceProfileInst spaceProfile, final String componentRole,
            final ComponentInstLight component) throws AdminException {
        final ProfileInst inheritedProfile;
        inheritedProfile = new ProfileInst();
        inheritedProfile.setComponentFatherId(component.getId());
        inheritedProfile.setName(componentRole);
        inheritedProfile.setInherited(true);
        inheritedProfile.addGroups(spaceProfile.getAllGroups());
        inheritedProfile.addUsers(spaceProfile.getAllUsers());
        if (inheritedProfile.getNumGroup() > 0 || inheritedProfile.getNumUser() > 0) {
            addProfileInst(inheritedProfile);
        }
    }

    private void updateInheritedProfileInsts(final int spaceId, final SpaceProfileInst spaceProfile,
            final ProfileInst inheritedProfile, final String componentRole, final ComponentInstLight component)
            throws AdminException {
        inheritedProfile.removeAllGroups();
        inheritedProfile.removeAllUsers();

        inheritedProfile.addGroups(spaceProfile.getAllGroups());
        inheritedProfile.addUsers(spaceProfile.getAllUsers());

        List<String> profilesToCheck = componentRole2SpaceRoles(componentRole, component.getName());
        profilesToCheck.remove(spaceProfile.getName()); // exclude current space profile
        for (String profileToCheck : profilesToCheck) {
            SpaceProfileInst spi = spaceProfileManager.getSpaceProfileInstByName(spaceId, profileToCheck);
            if (spi != null) {
                inheritedProfile.addGroups(spi.getAllGroups());
                inheritedProfile.addUsers(spi.getAllUsers());
            }
        }
        updateProfileInst(inheritedProfile);
    }

    // -------------------------------------------------------------------------
    // GROUP RELATED FUNCTIONS
    // -------------------------------------------------------------------------
    @Override
    public String[] getGroupNames(String[] groupIds) throws AdminException {
        if (groupIds == null) {
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }
        String[] asGroupNames = new String[groupIds.length];
        for (int nI = 0; nI < groupIds.length; nI++) {
            asGroupNames[nI] = getGroupName(groupIds[nI]);
        }
        return asGroupNames;
    }

    @Override
    public String getGroupName(String sGroupId) throws AdminException {
        return getGroup(sGroupId).getName();
    }

    @Override
    public List<GroupDetail> getAllGroups() throws AdminException {
        return groupManager.getAllGroups();
    }

    @Override
    public boolean isGroupExist(String groupName) throws AdminException {
        return groupManager.isGroupExist(groupName);
    }

    @Override
    public GroupDetail getGroup(String groupId) throws AdminException {
        return groupManager.getGroup(groupId);
    }

    @Override
    public List<String> getPathToGroup(String groupId) throws AdminException {
        return groupManager.getPathToGroup(groupId);
    }

    @Override
    public GroupDetail getGroupByNameInDomain(String groupName, String domainFatherId) throws AdminException {
        return groupManager.getGroupByNameInDomain(groupName, domainFatherId);
    }

    @Override
    public GroupDetail[] getGroups(String[] asGroupId) throws AdminException {
        if (asGroupId == null) {
            return new GroupDetail[0];
        }
        GroupDetail[] aGroup = new GroupDetail[asGroupId.length];
        for (int nI = 0; nI < asGroupId.length; nI++) {
            aGroup[nI] = getGroup(asGroupId[nI]);
        }
        return aGroup;
    }

    @Override
    public String addGroup(GroupDetail group) throws AdminException {
        try {
            return addGroup(group, false);
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(GROUP, group.getName()), e);
        }
    }

    @Override
    public String addGroup(GroupDetail group, boolean onlyInSilverpeas) throws AdminException {
        try {
            String sGroupId = groupManager.addGroup(group, onlyInSilverpeas);
            group.setId(sGroupId);
            if (group.isSynchronized()) {
                groupSynchroScheduler.addGroup(sGroupId);
            }
            cache.opAddGroup(group);
            return sGroupId;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(GROUP, group.getName()), e);
        }
    }

    @Override
    public String deleteGroupById(String sGroupId) throws AdminException {
        try {
            return deleteGroupById(sGroupId, false);
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(GROUP, sGroupId), e);
        }
    }

    @Override
    public String deleteGroupById(String sGroupId, boolean onlyInSilverpeas) throws AdminException {
        // Get group information
        GroupDetail group = getGroup(sGroupId);
        if (group == null) {
            throw new AdminException(unknown(GROUP, sGroupId));
        }
        try {

            // Delete group profiles
            deleteGroupProfileInst(sGroupId);

            // Listing the group and its sub groups before the recursive deletion
            final List<GroupDetail> groupAndSubGroups = new ArrayList<>();
            groupAndSubGroups.add(group);
            Collections.addAll(groupAndSubGroups, getRecursivelyAllSubGroups(sGroupId));

            // Delete group itself
            String sReturnGroupId = groupManager.deleteGroup(group, onlyInSilverpeas);

            // Removing the deleted groups from caches
            groupAndSubGroups.forEach(g -> {
                if (g.isSynchronized()) {
                    groupSynchroScheduler.removeGroup(g.getId());
                }
                cache.opRemoveGroup(g);
            });
            return sReturnGroupId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(GROUP, group.getId()), e);
        }
    }

    @Override
    public String updateGroup(GroupDetail group) throws AdminException {
        try {
            return updateGroup(group, false);
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(GROUP, group.getId()), e);
        }
    }

    @Override
    public String updateGroup(GroupDetail group, boolean onlyInSilverpeas) throws AdminException {
        try {
            String sGroupId = groupManager.updateGroup(group, onlyInSilverpeas);
            cache.resetOnUpdateGroup();
            return sGroupId;
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(GROUP, group.getId()), e);
        }
    }

    @Override
    public void removeUserFromGroup(String sUserId, String sGroupId) throws AdminException {
        try {
            // Update group
            groupManager.removeUserFromGroup(sUserId, sGroupId);

            cache.resetOnUpdateGroup();

        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(USER + sUserId, IN_GROUP + sGroupId), e);
        }
    }

    @Override
    public void addUserInGroup(String sUserId, String sGroupId) throws AdminException {
        try {
            // Update group
            groupManager.addUserInGroup(sUserId, sGroupId);
            cache.resetOnUpdateGroup();
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(USER + sUserId, IN_GROUP + sGroupId), e);
        }
    }

    @Override
    public List<GroupDetail> getAllRootGroups() throws AdminException {
        return groupManager.getAllRootGroups();
    }

    //
    // --------------------------------------------------------------------------------------------------------
    // GROUP PROFILE RELATED FUNCTIONS
    // --------------------------------------------------------------------------------------------------------
    @Override
    public GroupProfileInst getGroupProfileInst(String groupId) throws AdminException {
        return groupProfileManager.getGroupProfileInst(null, groupId);
    }

    @Override
    public String addGroupProfileInst(GroupProfileInst groupProfileInst) throws AdminException {
        try {
            // Create the space profile instance
            GroupDetail group = getGroup(groupProfileInst.getGroupId());
            String sProfileId = groupProfileManager.createGroupProfileInst(groupProfileInst, group.getId());
            groupProfileInst.setId(sProfileId);
            return sProfileId;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(GROUP_PROFILE, groupProfileInst.getName()), e);
        }
    }

    @Override
    public String deleteGroupProfileInst(String groupId) throws AdminException {
        // Get the SpaceProfile to delete
        GroupProfileInst groupProfileInst = groupProfileManager.getGroupProfileInst(null, groupId);
        if (groupProfileInst == null) {
            return groupId;
        }
        try {
            // Delete the Profile in tables
            groupProfileManager.deleteGroupProfileInst(groupProfileInst);
            return groupId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(GROUP_PROFILE, groupId), e);
        }
    }

    @Override
    public String updateGroupProfileInst(GroupProfileInst groupProfileInstNew) throws AdminException {
        String sSpaceProfileNewId = groupProfileInstNew.getId();
        if (!StringUtil.isDefined(sSpaceProfileNewId)) {
            sSpaceProfileNewId = addGroupProfileInst(groupProfileInstNew);
        } else {
            try {
                GroupProfileInst oldSpaceProfile = groupProfileManager.getGroupProfileInst(null,
                        groupProfileInstNew.getGroupId());
                // Update the group profile in tables
                groupProfileManager.updateGroupProfileInst(oldSpaceProfile, groupProfileInstNew);
            } catch (Exception e) {
                throw new AdminException(failureOnUpdate(GROUP_PROFILE, groupProfileInstNew.getId()), e);
            }
        }
        return sSpaceProfileNewId;

    }

    @Override
    public void indexAllGroups() throws AdminException {
        Domain[] domains = getAllDomains(); //All domains except Mixt Domain (id -1)
        for (Domain domain : domains) {
            try {
                indexGroups(domain.getId());
            } catch (Exception e) {
                SilverLogger.getLogger(this).error(e);
            }
        }

        //Mixt Domain (id -1)
        try {
            indexGroups("-1");
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
        }
    }

    @Override
    public void indexGroups(String domainId) throws AdminException {
        try {
            domainDriverManager.indexAllGroups(domainId);
        } catch (Exception e) {
            throw new AdminException(failureOnIndexing("groups in domain", domainId), e);
        }
    }

    // -------------------------------------------------------------------------
    // USER RELATED FUNCTIONS
    // -------------------------------------------------------------------------
    @Override
    public String[] getAllUsersIds() throws AdminException {
        List<String> userIds = userManager.getAllUsersIds();
        return userIds.toArray(new String[userIds.size()]);
    }

    @Override
    public UserDetail getUserDetail(String sUserId) throws AdminException {
        if (!StringUtil.isDefined(sUserId) || "-1".equals(sUserId)) {
            return null;
        }

        final UserDetail ud;
        Optional<UserDetail> optionalUser = cache.getUserDetail(sUserId);
        if (!optionalUser.isPresent()) {
            ud = userManager.getUserDetail(sUserId);
            if (ud != null) {
                cache.putUserDetail(sUserId, ud);
            }
        } else {
            ud = optionalUser.get();
        }
        return ud;
    }

    @Override
    public UserDetail[] getUserDetails(String[] userIds) {
        if (userIds == null) {
            return new UserDetail[0];
        }

        List<UserDetail> users = new ArrayList<>(userIds.length);
        for (String userId : userIds) {
            try {
                users.add(getUserDetail(userId));
            } catch (AdminException e) {
                SilverLogger.getLogger(this).error(e);
            }
        }
        return users.toArray(new UserDetail[users.size()]);
    }

    @Override
    public List<UserDetail> getAllUsers() throws AdminException {
        return userManager.getAllUsers();
    }

    @Override
    public List<UserDetail> getAllUsersFromNewestToOldest() throws AdminException {
        return userManager.getAllUsersFromNewestToOldest();
    }

    @Override
    public boolean isEmailExisting(String email) throws AdminException {
        return userManager.isEmailExisting(email);
    }

    @Override
    public String getUserIdByLoginAndDomain(String sLogin, String sDomainId) throws AdminException {
        Domain[] theDomains;
        String valret = null;
        if (!StringUtil.isDefined(sDomainId)) {
            try {
                theDomains = domainDriverManager.getAllDomains();
            } catch (Exception e) {
                throw new AdminException(failureOnGetting("user by login and domain:", sLogin + "/" + sDomainId),
                        e);
            }
            for (int i = 0; i < theDomains.length && valret == null; i++) {
                try {
                    valret = userManager.getUserIdByLoginAndDomain(sLogin, theDomains[i].getId());
                } catch (Exception e) {
                    throw new AdminException(
                            failureOnGetting("user by login and domain:", sLogin + "/" + sDomainId), e);
                }
            }
            if (valret == null) {
                throw new AdminException(unknown("in all domains user with login", sLogin));
            }
        } else {
            valret = userManager.getUserIdByLoginAndDomain(sLogin, sDomainId);
        }
        return valret;
    }

    @Override
    public String getUserIdByAuthenticationKey(String authenticationKey) throws AdminException {
        Map<String, String> userParameters = domainDriverManager.authenticate(authenticationKey);
        String login = userParameters.get(LOGIN_PARAM);
        String domainId = userParameters.get(DOMAIN_ID_PARAM);
        return userManager.getUserIdByLoginAndDomain(login, domainId);
    }

    @Override
    public UserFull getUserFull(String sUserId) throws AdminException {
        return userManager.getUserFull(sUserId);
    }

    @Override
    public UserFull getUserFull(String domainId, String specificId) throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        return synchroDomain.getUserFull(specificId);
    }

    @Override
    public String addUser(UserDetail userDetail) throws AdminException {
        try {
            return addUser(userDetail, false);
        } catch (Exception e) {
            throw new AdminException(failureOnAdding("user", userDetail.getDisplayedName()), e);
        }
    }

    @Override
    public String addUser(UserDetail userDetail, boolean addOnlyInSilverpeas) throws AdminException {
        try {
            // add user
            return userManager.addUser(userDetail, addOnlyInSilverpeas, true);
        } catch (Exception e) {
            throw new AdminException(failureOnAdding("user", userDetail.getDisplayedName()), e);
        }
    }

    @Override
    public void migrateUser(UserDetail userDetail, String targetDomainId) throws AdminException {
        try {
            userManager.migrateUser(userDetail, targetDomainId);
            cache.opUpdateUser(userDetail);
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(USER + userDetail.getId(), "in domain " + targetDomainId), e);
        }
    }

    @Override
    public void blockUser(String userId) throws AdminException {
        updateUserState(userId, UserState.BLOCKED);
    }

    @Override
    public void unblockUser(String userId) throws AdminException {
        updateUserState(userId, UserState.VALID);
    }

    /**
     * Deactivates the user represented by the given identifier.
     *
     * @param userId
     * @throws AdminException
     */
    @Override
    public void deactivateUser(String userId) throws AdminException {
        updateUserState(userId, UserState.DEACTIVATED);
    }

    /**
     * Activate the user represented by the given identifier.
     *
     * @param userId
     * @throws AdminException
     */
    @Override
    public void activateUser(String userId) throws AdminException {
        updateUserState(userId, UserState.VALID);
    }

    /**
     * Updates the user state from a user id.
     *
     * @param userId
     * @param state
     * @throws AdminException
     */
    private void updateUserState(String userId, UserState state) throws AdminException {
        try {
            UserDetail user = UserDetail.getById(userId);
            user.setState(state);
            user.setStateSaveDate(new Date());
            updateUser(user);
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate("state of user " + userId, "to " + state.getName()), e);
        }
    }

    @Override
    public void userAcceptsTermsOfService(String userId) throws AdminException {
        try {
            UserDetail user = UserDetail.getById(userId);
            user.setTosAcceptanceDate(DateUtil.getNow());
            updateUser(user);
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate("terms of service acceptance for user", userId), e);
        }
    }

    @Override
    public String restoreUser(final String sUserId) throws AdminException {
        final UserDetail user = getUserDetail(sUserId);
        if (user == null) {
            throw new AdminException(unknown("user", sUserId));
        }
        try {
            final String removedUserId = userManager.restoreUser(user, true);
            cache.resetCache();
            return removedUserId;
        } catch (Exception e) {
            throw new AdminException(failureOnRemoving("user", sUserId), e);
        }
    }

    @Override
    public String removeUser(final String sUserId) throws AdminException {
        if (ADMIN_ID.equals(sUserId)) {
            SilverLogger.getLogger(this).warn("Attempt to remove the main administrator account by user "
                    + User.getCurrentRequester().getId());
            return null;
        }
        final UserDetail user = getUserDetail(sUserId);
        if (user == null) {
            throw new AdminException(unknown("user", sUserId));
        }
        try {
            final String removedUserId = userManager.removeUser(user, true);
            cache.opRemoveUser(user);
            return removedUserId;
        } catch (Exception e) {
            throw new AdminException(failureOnRemoving("user", sUserId), e);
        }
    }

    @Override
    public String deleteUser(String sUserId) throws AdminException {
        try {
            if (ADMIN_ID.equals(sUserId)) {
                SilverLogger.getLogger(this).warn("Attempt to delete the main administrator account by user "
                        + User.getCurrentRequester().getId());
                return null;
            }

            return deleteUser(sUserId, false);

        } catch (Exception e) {
            throw new AdminException(failureOnDeleting("user", sUserId), e);
        }
    }

    @Override
    public String deleteUser(String sUserId, boolean onlyInSilverpeas) throws AdminException {
        UserDetail user;
        try {
            user = getUserDetail(sUserId);
            if (user == null) {
                throw new AdminException(unknown("user", sUserId));
            }

            // Delete the user
            String sReturnUserId = userManager.deleteUser(user, onlyInSilverpeas);

            cache.opRemoveUser(user);
            return sReturnUserId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting("user", sUserId), e);
        }
    }

    @Override
    public String updateUser(UserDetail user) throws AdminException {
        try {
            // Update user
            String sUserId = userManager.updateUser(user, true);

            cache.opUpdateUser(userManager.getUserDetail(sUserId));

            return sUserId;
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate("user", user.getId()), e);
        }
    }

    @Override
    public String updateUserFull(UserFull user) throws AdminException {
        try {
            // Update user
            String sUserId = userManager.updateUserFull(user);

            cache.opUpdateUser(userManager.getUserDetail(sUserId));

            return sUserId;
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate("user", user.getId()), e);
        }
    }

    // -------------------------------------------------------------------------
    // CONVERSION CLIENT <--> DRIVER SPACE ID
    // -------------------------------------------------------------------------
    /**
     * Converts client space id to driver space id
     */
    private int getDriverSpaceId(String sClientSpaceId) {
        String clientSpaceId = sClientSpaceId;
        if (clientSpaceId != null) {
            if (clientSpaceId.startsWith(SpaceInst.SPACE_KEY_PREFIX)) {
                clientSpaceId = clientSpaceId.substring(SpaceInst.SPACE_KEY_PREFIX.length());
            }
            try {
                return Integer.parseInt(clientSpaceId);
            } catch (NumberFormatException e) {
                SilverLogger.getLogger(this).warn("can not get driver space id from {0} : {1}", sClientSpaceId,
                        e.getMessage());
            }
        }
        return -1;
    }

    @Override
    public String getClientSpaceId(String sDriverSpaceId) {
        if (sDriverSpaceId != null && !sDriverSpaceId.startsWith(SpaceInst.SPACE_KEY_PREFIX)) {
            return SpaceInst.SPACE_KEY_PREFIX + sDriverSpaceId;
        }
        return sDriverSpaceId;
    }

    @Override
    public String[] getClientSpaceIds(String[] asDriverSpaceIds) {
        String[] asClientSpaceIds = new String[asDriverSpaceIds.length];
        for (int nI = 0; nI < asDriverSpaceIds.length; nI++) {
            asClientSpaceIds[nI] = getClientSpaceId(asDriverSpaceIds[nI]);
        }
        return asClientSpaceIds;
    }

    private Integer getDriverComponentId(String sClientComponentId) {
        if (sClientComponentId == null) {
            return null;
        }

        return getTableClientComponentIdFromClientComponentId(sClientComponentId);
    }

    /**
     * @return 23 for parameter kmelia23
     */
    private Integer getTableClientComponentIdFromClientComponentId(String sClientComponentId) {
        String sTableClientId = "";

        // Remove the component name to get the table client id
        char[] cBuf = sClientComponentId.toCharArray();
        if (Character.isDigit(cBuf[cBuf.length - 1])) {
            for (int nI = 0; nI < cBuf.length && sTableClientId.length() == 0; nI++) {
                if (Character.isDigit(cBuf[nI])) {
                    sTableClientId = sClientComponentId.substring(nI);
                }
            }
            if (StringUtil.isDefined(sTableClientId)) {
                return Integer.parseInt(sTableClientId);
            }
        }
        return -1;
    }

    /**
     * Return kmelia23 for parameter 23
     */
    private String getClientComponentId(ComponentInst component) {
        return getClientComponentId(component.getName(), component.getId());
    }

    private String getClientComponentId(String componentName, String sDriverComponentId) {
        if (StringUtil.isInteger(sDriverComponentId)) {
            return componentName + sDriverComponentId;
        }
        // id is already in client format
        return sDriverComponentId;
    }

    // -------------------------------------------------------------------------
    // DOMAIN QUERY
    // -------------------------------------------------------------------------
    @Override
    public String getNextDomainId() throws AdminException {
        try {
            return domainDriverManager.getNextDomainId();
        } catch (Exception e) {
            throw new AdminException(e.getMessage(), e);
        }
    }

    @Override
    public String addDomain(Domain theDomain) throws AdminException {
        try {
            String id = domainDriverManager.createDomain(theDomain);

            // Update the synchro scheduler
            DomainDriver domainDriver = domainDriverManager.getDomainDriver(id);
            if (domainDriver.isSynchroThreaded()) {
                domainSynchroScheduler.addDomain(id);
            }

            return id;
        } catch (Exception e) {
            throw new AdminException(failureOnAdding(DOMAIN, theDomain.getName()), e);
        }
    }

    @Override
    public String updateDomain(Domain domain) throws AdminException {
        try {
            domainCache.removeDomain(domain.getId());
            return domainDriverManager.updateDomain(domain);
        } catch (Exception e) {
            throw new AdminException(failureOnUpdate(DOMAIN, domain.getId()), e);
        }
    }

    @Override
    public String removeDomain(String domainId) throws AdminException {
        try {
            // Remove all users
            UserDetail[] toRemoveUDs = userManager.getAllUsersInDomain(domainId, true);
            if (toRemoveUDs != null) {
                SilverLogger.getLogger(this).debug("[Domain deletion] Remove all the users...");
                for (final UserDetail user : toRemoveUDs) {
                    deleteUser(user.getId(), false);
                }
            }

            // Remove all groups
            GroupDetail[] toRemoveGroups = groupManager.getRootGroupsOfDomain(domainId);
            if (toRemoveGroups != null) {
                SilverLogger.getLogger(this).debug("[Domain deletion] Remove all the groups...");
                for (final GroupDetail group : toRemoveGroups) {
                    deleteGroupById(group.getId(), false);
                }
            }

            SilverLogger.getLogger(this).debug("[Domain deletion] Then remove the domain...");
            // Remove the domain
            domainDriverManager.removeDomain(domainId);
            // Update the synchro scheduler
            domainSynchroScheduler.removeDomain(domainId);
            domainCache.removeDomain(domainId);

            return domainId;
        } catch (Exception e) {
            throw new AdminException(failureOnDeleting(DOMAIN, domainId), e);
        }
    }

    @Override
    public Domain[] getAllDomains() throws AdminException {
        try {
            return domainDriverManager.getAllDomains();
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all domains", "in Silverpeas"), e);
        }
    }

    @Override
    public List<String> getAllDomainIdsForLogin(String login) throws AdminException {
        return userManager.getDomainsByUserLogin(login);
    }

    @Override
    public Domain getDomain(String domainId) throws AdminException {
        try {
            if (!StringUtil.isDefined(domainId) || !StringUtil.isInteger(domainId)) {
                domainId = "-1";
            }
            final Domain domain;
            Optional<Domain> optionalDomain = domainCache.getDomain(domainId);
            if (!optionalDomain.isPresent()) {
                domain = domainDriverManager.getDomain(domainId);
                domainCache.addDomain(domain);
            } else {
                domain = optionalDomain.get();
            }
            return domain;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(DOMAIN, domainId), e);
        }
    }

    @Override
    public long getDomainActions(String domainId) throws AdminException {
        try {
            if (domainId != null && domainId.equals("-1")) {
                return ACTION_MASK_MIXED_GROUPS;
            }
            return domainDriverManager.getDomainActions(domainId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("actions of domain", domainId), e);
        }
    }

    @Override
    public GroupDetail[] getRootGroupsOfDomain(String domainId) throws AdminException {
        try {
            return groupManager.getRootGroupsOfDomain(domainId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("root groups of domain", domainId), e);
        }
    }

    @Override
    public List<GroupDetail> getSynchronizedGroups() throws AdminException {
        try {
            return groupManager.getSynchronizedGroups();
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("synchronized groups", ""), e);
        }
    }

    @Override
    public UserDetail[] getAllUsersOfGroup(String groupId) throws AdminException {
        try {
            List<String> groupIds = new ArrayList<>();
            groupIds.add(groupId);
            groupIds.addAll(groupManager.getAllSubGroupIdsRecursively(groupId));

            return userManager.getAllUsersInGroups(groupIds);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all users in group", groupId), e);
        }
    }

    @Override
    public UserDetail[] getUsersOfDomain(String domainId) throws AdminException {
        try {
            if ("-1".equals(domainId)) {
                return new UserDetail[0];
            }
            return userManager.getAllUsersInDomain(domainId, false);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all users in domain", domainId), e);
        }
    }

    @Override
    public List<UserDetail> getUsersOfDomains(List<String> domainIds) throws AdminException {
        return userManager.getUsersOfDomains(domainIds);
    }

    @Override
    public List<UserDetail> getUsersOfDomainsFromNewestToOldest(List<String> domainIds) throws AdminException {
        return userManager.getUsersOfDomainsFromNewestToOldest(domainIds);
    }

    @Override
    public String[] getUserIdsOfDomain(String domainId) throws AdminException {
        try {
            if ("-1".equals(domainId)) {
                return ArrayUtil.EMPTY_STRING_ARRAY;
            }
            List<String> userIds = userManager.getAllUserIdsInDomain(domainId);
            return userIds.toArray(new String[0]);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("all users in domain", domainId), e);
        }
    }

    // -------------------------------------------------------------------------
    // USERS QUERY
    // -------------------------------------------------------------------------
    @Override
    public String identify(String sKey, String sSessionId, boolean isAppInMaintenance) throws AdminException {
        return identify(sKey, sSessionId, isAppInMaintenance, true);
    }

    @Override
    public String identify(String sKey, String sSessionId, boolean isAppInMaintenance, boolean removeKey)
            throws AdminException {
        String sUserId;
        try {
            // Authenticate the given user
            Map<String, String> loginDomain = domainDriverManager.authenticate(sKey, removeKey);
            if ((!loginDomain.containsKey(LOGIN_PARAM)) || (!loginDomain.containsKey(DOMAIN_ID_PARAM))) {
                throw new AdminException(undefined("domain for authentication key " + sKey));
            }

            // Get the Silverpeas userId
            String sLogin = loginDomain.get(LOGIN_PARAM);
            String sDomainId = loginDomain.get(DOMAIN_ID_PARAM);

            DomainDriver synchroDomain = domainDriverManager.getDomainDriver(sDomainId);
            // Get the user Id or import it if the domain accept it
            sUserId = synchroGetUserId(isAppInMaintenance, sLogin, sDomainId, synchroDomain);
            // Synchronize the user if the domain needs it
            doSynchronizeUser(sUserId, synchroDomain, isAppInMaintenance);

            return sUserId;
        } catch (Exception e) {
            throw new AdminException("Fail to identify authentication key " + sKey, e);
        }
    }

    private void doSynchronizeUser(final String sUserId, final DomainDriver synchroDomain,
            final boolean isAppInMaintenance) {
        if (synchroDomain.isSynchroOnLoginEnabled() && !isAppInMaintenance) {
            try {
                synchronizeUser(sUserId, synchroDomain.isSynchroOnLoginRecursToGroups());
            } catch (Exception ex) {
                SilverLogger.getLogger(this).error(ex);
            }
        }
    }

    private String synchroGetUserId(final boolean isAppInMaintenance, final String sLogin, final String sDomainId,
            final DomainDriver synchroDomain) throws AdminException {
        String sUserId;
        try {
            sUserId = userManager.getUserIdByLoginAndDomain(sLogin, sDomainId);
        } catch (Exception ex) {
            if (synchroDomain.isSynchroOnLoginEnabled() && !isAppInMaintenance) {//Try to import new user
                SilverLogger.getLogger(this).warn("User with login {0} in domain {1} not found", sLogin, sDomainId);
                sUserId = synchronizeImportUserByLogin(sDomainId, sLogin,
                        synchroDomain.isSynchroOnLoginRecursToGroups());
            } else {
                throw ex;
            }
        }
        return sUserId;
    }

    // ---------------------------------------------------------------------------------------------
    // QUERY FUNCTIONS
    // ---------------------------------------------------------------------------------------------
    @Override
    public List<GroupDetail> getDirectGroupsOfUser(String userId) throws AdminException {
        try {
            return groupManager.getDirectGroupsOfUser(userId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("direct groups of user", userId), e);
        }
    }

    @Override
    public String[] getUserSpaceIds(String sUserId) throws AdminException {
        List<String> spaceIds = new ArrayList<>();

        // getting all components availables
        List<String> componentIds = getAllowedComponentIds(sUserId);
        for (String componentId : componentIds) {
            List<SpaceInstLight> spaces = treeCache.getComponentPath(componentId);
            for (SpaceInstLight space : spaces) {
                if (!spaceIds.contains(space.getId())) {
                    spaceIds.add(space.getId());
                }
            }
        }

        return spaceIds.toArray(new String[spaceIds.size()]);
    }

    private List<String> getAllGroupsOfUser(String userId) throws AdminException {
        final List<String> allGroupsOfUser;
        Optional<List<String>> optionalGroups = groupCache.getAllGroupIdsOfUser(userId);
        if (!optionalGroups.isPresent()) {
            // group ids of user is not yet processed
            // process it and store it in cache
            allGroupsOfUser = groupManager.getAllGroupsOfUser(userId);
            // store groupIds of user in cache
            groupCache.setAllGroupIdsOfUser(userId, allGroupsOfUser);
        } else {
            allGroupsOfUser = optionalGroups.get();
        }
        return allGroupsOfUser;
    }

    private List<String> getAllowedComponentIds(String userId) throws AdminException {
        return getAllowedComponentIds(userId, null);
    }

    private List<String> getAllowedComponentIds(String userId, String componentName) throws AdminException {
        // getting all groups of users
        List<String> allGroupsOfUser = getAllGroupsOfUser(userId);

        return componentManager.getAllowedComponentIds(Integer.parseInt(userId), allGroupsOfUser, null,
                componentName);
    }

    @Override
    public String[] getUserRootSpaceIds(String sUserId) throws AdminException {
        try {
            // getting all components availables
            List<String> componentIds = getAllowedComponentIds(sUserId);
            return getUserRootSpaceIds(componentIds);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("root spaces accessible by user", sUserId), e);
        }
    }

    private String[] getUserRootSpaceIds(List<String> componentIds) throws AdminException {
        List<String> result = new ArrayList<>();
        // getting all root spaces (sorted)
        String[] rootSpaceIds = getAllRootSpaceIds();
        // retain only allowed root spaces
        for (String rootSpaceId : rootSpaceIds) {
            if (isSpaceContainsOneComponent(componentIds, getDriverSpaceId(rootSpaceId), true)) {
                result.add(rootSpaceId);
            }
        }
        return result.toArray(new String[result.size()]);
    }

    @Override
    public String[] getUserSubSpaceIds(String sUserId, String spaceId) throws AdminException {
        try {
            // getting all components availables
            List<String> componentIds = getAllowedComponentIds(sUserId);
            return getUserSubSpaceIds(componentIds, spaceId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SUBSPACES_OF_SPACE + spaceId, ACCESSIBLE_BY_USER + sUserId),
                    e);
        }
    }

    private String[] getUserSubSpaceIds(List<String> componentIds, String spaceId) {
        List<String> result = new ArrayList<>();
        // getting all subspaces
        List<SpaceInstLight> subspaces = treeCache.getSubSpaces(getDriverSpaceId(spaceId));
        for (SpaceInstLight subspace : subspaces) {
            if (isSpaceContainsOneComponent(componentIds, subspace.getLocalId(), true)) {
                result.add(subspace.getId());
            }
        }
        return result.toArray(new String[result.size()]);
    }

    @Override
    public boolean isSpaceAvailable(String userId, String spaceId) throws AdminException {
        List<String> componentIds = getAllowedComponentIds(userId);
        return isSpaceAvailable(componentIds, spaceId);
    }

    private boolean isSpaceAvailable(List<String> componentIds, String spaceId) {
        return isSpaceContainsOneComponent(componentIds, getDriverSpaceId(spaceId), true);
    }

    private boolean isSpaceContainsOneComponent(List<String> componentIds, int spaceId, boolean checkInSubspaces) {
        boolean find = false;

        List<ComponentInstLight> components = new ArrayList<>(treeCache.getComponents(spaceId));

        // Is there at least one component available ?
        for (int c = 0; !find && c < components.size(); c++) {
            find = componentIds.contains(components.get(c).getId());
        }
        if (find) {
            return true;
        } else {
            if (checkInSubspaces) {
                // check in subspaces
                List<SpaceInstLight> subspaces = new ArrayList<>(treeCache.getSubSpaces(spaceId));
                for (int s = 0; !find && s < subspaces.size(); s++) {
                    find = isSpaceContainsOneComponent(componentIds, subspaces.get(s).getLocalId(),
                            checkInSubspaces);
                }
            }
        }

        return find;
    }

    @Override
    public List<SpaceInstLight> getSubSpacesOfUser(String userId, String spaceId) throws AdminException {

        try {
            List<SpaceInstLight> result = new ArrayList<>();

            // getting all components availables
            List<String> componentIds = getAllowedComponentIds(userId);

            // getting all subspaces
            List<SpaceInstLight> subspaces = treeCache.getSubSpaces(getDriverSpaceId(spaceId));
            for (SpaceInstLight subspace : subspaces) {
                if (isSpaceContainsOneComponent(componentIds, subspace.getLocalId(), true)) {
                    result.add(subspace);
                }
            }

            return result;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SUBSPACES_OF_SPACE + spaceId, ACCESSIBLE_BY_USER + userId),
                    e);
        }
    }

    @Override
    public List<SpaceInstLight> getSubSpaces(String spaceId) throws AdminException {
        return spaceManager.getSubSpaces(getDriverSpaceId(spaceId));
    }

    @Override
    public List<ComponentInstLight> getAvailCompoInSpace(String userId, String spaceId) throws AdminException {

        try {
            List<String> allowedComponentIds = getAllowedComponentIds(userId);

            List<ComponentInstLight> allowedComponents = new ArrayList<>();

            List<ComponentInstLight> allComponents = treeCache
                    .getComponentsInSpaceAndSubspaces(getDriverSpaceId(spaceId));
            for (ComponentInstLight component : allComponents) {
                if (allowedComponentIds.contains(component.getId())) {
                    allowedComponents.add(component);
                }
            }
            return allowedComponents;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(COMPONENTS_IN_SPACE + spaceId, ACCESSIBLE_BY_USER + userId),
                    e);
        }
    }

    @Override
    public Map<String, SpaceAndChildren> getTreeView(String userId, String spaceId) throws AdminException {

        int driverSpaceId = getDriverSpaceId(spaceId);

        // Step 1 - get all availables spaces and components
        Collection<SpaceInstLight> spacesLight = getSubSpacesOfUser(userId, spaceId);
        Collection<ComponentInstLight> componentsLight = getAvailCompoInSpace(userId, spaceId);

        // Step 2 - build HashTable
        Map<String, SpaceAndChildren> spaceTrees = new HashMap<>();
        Iterator<SpaceInstLight> it = spacesLight.iterator();
        while (it.hasNext()) {
            SpaceInstLight space = it.next();
            spaceTrees.put(space.getId(), new SpaceAndChildren(space));
        }

        // Step 3 - add root space to hashtable
        SpaceInstLight rootSpace = getSpaceInstLight(driverSpaceId);
        if (rootSpace == null) {
            throw new AdminException("No such space " + spaceId);
        }
        spaceTrees.put(rootSpace.getId(), new SpaceAndChildren(rootSpace));

        // Step 4 - build dependances
        it = spacesLight.iterator();
        while (it.hasNext()) {
            SpaceInstLight child = it.next();
            String fatherId = getClientSpaceId(child.getFatherId());
            SpaceAndChildren father = spaceTrees.get(fatherId);
            if (father != null) {
                father.addSubSpace(child);
            }
        }

        for (ComponentInstLight child : componentsLight) {
            String fatherId = getClientSpaceId(child.getDomainFatherId());
            SpaceAndChildren father = spaceTrees.get(fatherId);
            if (father != null) {
                father.addComponent(child);
            }
        }

        return spaceTrees;
    }

    @Override
    public List<SpaceInstLight> getUserSpaceTreeview(String userId) throws AdminException {

        Set<String> componentsId = new HashSet<>(Arrays.asList(getAvailCompoIds(userId)));
        Set<Integer> authorizedIds = new HashSet<>(100);
        if (!componentsId.isEmpty()) {
            String componentId = componentsId.iterator().next();
            componentsId.remove(componentId);
            filterSpaceFromComponents(authorizedIds, componentsId, componentId);
        }
        String[] rootSpaceIds = getAllRootSpaceIds(userId);
        List<SpaceInstLight> treeview = new ArrayList<>(authorizedIds.size());
        for (String spaceId : rootSpaceIds) {
            int currentSpaceId = getDriverSpaceId(spaceId);
            if (authorizedIds.contains(currentSpaceId)) {
                Optional<SpaceInstLight> optionalSpace = treeCache.getSpaceInstLight(currentSpaceId);
                optionalSpace.ifPresent(s -> {
                    treeview.add(s);
                    addAuthorizedSpaceToTree(treeview, authorizedIds, currentSpaceId, 1);
                });
            }
        }
        return treeview;
    }

    void addAuthorizedSpaceToTree(List<SpaceInstLight> treeview, Set<Integer> authorizedIds, int spaceId,
            int level) {
        List<SpaceInstLight> subSpaces = treeCache.getSubSpaces(spaceId);
        for (SpaceInstLight space : subSpaces) {
            int subSpaceId = space.getLocalId();
            if (authorizedIds.contains(subSpaceId)) {
                space.setLevel(level);
                treeview.add(space);
                addAuthorizedSpaceToTree(treeview, authorizedIds, subSpaceId, level + 1);
            }
        }
    }

    /**
     * @param spaces list of authorized spaces built by this method
     * @param componentsId list of components' id (base to get authorized spaces)
     * @param space a space candidate to be in authorized spaces list
     */
    private void addAuthorizedSpace(Set<Integer> spaces, Set<String> componentsId, SpaceInstLight space) {
        if (!SpaceInst.STATUS_REMOVED.equals(space.getStatus()) && !spaces.contains(space.getLocalId())) {
            int spaceId = space.getLocalId();
            spaces.add(spaceId);
            componentsId.removeAll(treeCache.getComponentIds(spaceId));
            if (!space.isRoot()) {
                int fatherId = getDriverSpaceId(space.getFatherId());
                if (!spaces.contains(fatherId)) {
                    Optional<SpaceInstLight> parent = treeCache.getSpaceInstLight(fatherId);
                    parent.ifPresent(p -> addAuthorizedSpace(spaces, componentsId, p));
                }
            }
        }
    }

    private void filterSpaceFromComponents(Set<Integer> spaces, Set<String> componentsId, String componentId) {
        Optional<SpaceInstLight> space = treeCache.getSpaceContainingComponent(componentId);
        space.ifPresent(s -> addAuthorizedSpace(spaces, componentsId, s));
        if (!componentsId.isEmpty()) {
            String newComponentId = componentsId.iterator().next();
            componentsId.remove(newComponentId);
            filterSpaceFromComponents(spaces, componentsId, newComponentId);
        }
    }

    public SpaceWithSubSpacesAndComponents getAllowedFullTreeview(String userId) throws AdminException {
        SpaceWithSubSpacesAndComponents root = new SpaceWithSubSpacesAndComponents(new SpaceInstLight());
        List<String> componentIds = getAllowedComponentIds(userId);
        String[] spaceIds = getUserRootSpaceIds(componentIds);
        List<SpaceWithSubSpacesAndComponents> spaces = new ArrayList<>();
        for (String spaceId : spaceIds) {
            SpaceWithSubSpacesAndComponents space = getAllowedTreeview(componentIds, spaceId);
            spaces.add(space);
        }
        root.setSubSpaces(spaces);
        return root;
    }

    public SpaceWithSubSpacesAndComponents getAllowedFullTreeview(String userId, String spaceId)
            throws AdminException {
        List<String> componentIds = getAllowedComponentIds(userId);
        return getAllowedTreeview(componentIds, spaceId);
    }

    @Override
    public List<UserDetail> getRemovedUsers(final String... domainIds) throws AdminException {
        return userManager.getRemovedUsersOfDomains(domainIds);
    }

    @Override
    public List<UserDetail> getNonBlankedDeletedUsers(final String... domainIds) throws AdminException {
        return userManager.getNonBlankedDeletedUsersOfDomains(domainIds);
    }

    @Override
    public void blankDeletedUsers(final String targetDomainId, final List<String> userIds) throws AdminException {
        final UserDetail[] users = getUserDetails(userIds.toArray(new String[0]));
        for (final UserDetail user : users) {
            if (user.getDomainId().equals(targetDomainId)) {
                userManager.blankUser(user);
                cache.opUpdateUser(userManager.getUserDetail(user.getId()));
            }
        }
    }

    private SpaceWithSubSpacesAndComponents getAllowedTreeview(List<String> componentIds, String spaceId)
            throws AdminException {
        SpaceInstLight spaceInst = getSpaceInstLight(getDriverSpaceId(spaceId));
        SpaceWithSubSpacesAndComponents space = new SpaceWithSubSpacesAndComponents(spaceInst);

        // process subspaces
        List<SpaceWithSubSpacesAndComponents> subSpaces = new ArrayList<>();
        for (String subSpaceId : getUserSubSpaceIds(componentIds, spaceId)) {
            subSpaces.add(getAllowedTreeview(componentIds, subSpaceId));
        }
        space.setSubSpaces(subSpaces);

        // process components
        List<ComponentInstLight> allowedComponents = new ArrayList<>();
        List<ComponentInstLight> allComponents = treeCache.getComponents(getDriverSpaceId(spaceId));
        for (ComponentInstLight component : allComponents) {
            if (componentIds.contains(component.getId())) {
                allowedComponents.add(component);
            }
        }
        space.setComponents(allowedComponents);

        return space;
    }

    @Override
    public String[] getAllowedSubSpaceIds(String userId, String spaceFatherId) throws AdminException {
        return getUserSubSpaceIds(userId, spaceFatherId);
    }

    private SpaceInstLight getSpaceInstLight(int spaceId) throws AdminException {
        return getSpaceInstLight(spaceId, -1);
    }

    private SpaceInstLight getSpaceInstLight(int spaceId, int level) throws AdminException {
        Optional<SpaceInstLight> optionalSpace = treeCache.getSpaceInstLight(spaceId);
        final SpaceInstLight sil = optionalSpace.orElse(spaceManager.getSpaceInstLightById(spaceId));
        if (sil != null) {
            if (level != -1) {
                sil.setLevel(level);
            }
            if (sil.getLevel() == -1) {
                sil.setLevel(treeCache.getSpaceLevel(spaceId));
            }
        }
        return sil;
    }

    @Override
    public SpaceInstLight getSpaceInstLightById(String sClientSpaceId) throws AdminException {
        try {
            return getSpaceInstLight(getDriverSpaceId(sClientSpaceId));
        } catch (Exception e) {
            throw new AdminException(failureOnGetting(SPACE, sClientSpaceId), e);
        }
    }

    @Override
    public SpaceInstLight getRootSpace(String spaceId) throws AdminException {
        SpaceInstLight sil = getSpaceInstLight(getDriverSpaceId(spaceId));
        while (sil != null && !sil.isRoot()) {
            sil = getSpaceInstLight(getDriverSpaceId(sil.getFatherId()));
        }
        return sil;
    }

    @Override
    public String[] getGroupManageableSpaceIds(String sGroupId) throws AdminException {
        String[] asManageableSpaceIds;
        ArrayList<String> alManageableSpaceIds = new ArrayList<>();
        try {
            // Get user manageable space ids from database
            List<String> groupIds = new ArrayList<>();
            groupIds.add(sGroupId);
            List<Integer> manageableSpaceIds = spaceManager.getManageableSpaceIds(null, groupIds);

            // Inherits manageability rights for space children
            String[] childSpaceIds;
            for (Integer spaceId : manageableSpaceIds) {
                String asManageableSpaceId = String.valueOf(spaceId);
                // add manageable space id in result
                if (!alManageableSpaceIds.contains(asManageableSpaceId)) {
                    alManageableSpaceIds.add(asManageableSpaceId);
                }

                // calculate manageable space's childs
                childSpaceIds = spaceManager.getAllSubSpaceIds(spaceId);
                // add them in result
                for (String childSpaceId : childSpaceIds) {
                    if (!alManageableSpaceIds.contains(childSpaceId)) {
                        alManageableSpaceIds.add(childSpaceId);
                    }
                }
            }

            // Put user manageable space ids in cache
            asManageableSpaceIds = alManageableSpaceIds.toArray(new String[alManageableSpaceIds.size()]);

            return asManageableSpaceIds;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("spaces manageable by group", sGroupId), e);
        }
    }

    @Override
    public String[] getUserManageableSpaceIds(String sUserId) throws AdminException {
        final Integer[] result;
        ArrayList<String> alManageableSpaceIds = new ArrayList<>();
        ArrayList<Integer> alDriverManageableSpaceIds = new ArrayList<>();
        try {
            // Get user manageable space ids from cache
            Optional<Integer[]> optionalSpaceIds = cache.getManageableSpaceIds(sUserId);
            if (!optionalSpaceIds.isPresent()) {
                // Get user manageable space ids from database

                List<String> groupIds = getAllGroupsOfUser(sUserId);
                final Integer[] manageableSpaceIds = userManager.getManageableSpaceIds(sUserId, groupIds);

                // Inherits manageability rights for space children
                String[] childSpaceIds;
                for (Integer asManageableSpaceId : manageableSpaceIds) {
                    // add manageable space id in result
                    String asManageableSpaceIdAsString = String.valueOf(asManageableSpaceId);
                    if (!alManageableSpaceIds.contains(asManageableSpaceIdAsString)) {
                        alManageableSpaceIds.add(asManageableSpaceIdAsString);
                        alDriverManageableSpaceIds.add(asManageableSpaceId);
                    }

                    // calculate manageable space's childs
                    childSpaceIds = spaceManager.getAllSubSpaceIds(asManageableSpaceId);

                    // add them in result
                    for (String childSpaceId : childSpaceIds) {
                        if (!alManageableSpaceIds.contains(childSpaceId)) {
                            alManageableSpaceIds.add(childSpaceId);
                            alDriverManageableSpaceIds.add(getDriverSpaceId(childSpaceId));
                        }
                    }
                }

                // Put user manageable space ids in cache
                result = alDriverManageableSpaceIds.toArray(new Integer[alDriverManageableSpaceIds.size()]);
                cache.putManageableSpaceIds(sUserId, result);
            } else {
                result = optionalSpaceIds.get();
            }
            return Arrays.stream(result).map(String::valueOf).toArray(String[]::new);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("spaces manageable by user", sUserId), e);
        }
    }

    @Override
    public String[] getUserManageableSpaceRootIds(String sUserId) throws AdminException {
        try {
            // Get user manageable space ids from database
            List<String> groupIds = getAllGroupsOfUser(sUserId);
            Integer[] asManageableSpaceIds = userManager.getManageableSpaceIds(sUserId, groupIds);

            // retain only root spaces
            List<String> manageableRootSpaceIds = new ArrayList<>();
            for (Integer asManageableSpaceId : asManageableSpaceIds) {
                Optional<SpaceInstLight> space = treeCache.getSpaceInstLight(asManageableSpaceId);
                space.filter(SpaceInstLight::isRoot)
                        .ifPresent(s -> manageableRootSpaceIds.add(asManageableSpaceId.toString()));
            }
            return manageableRootSpaceIds.toArray(new String[0]);

        } catch (Exception e) {
            throw new AdminException(failureOnGetting("root spaces manageable by user", sUserId), e);
        }
    }

    @Override
    public String[] getUserManageableSubSpaceIds(String sUserId, String sParentSpaceId) throws AdminException {
        try {
            // Get user manageable space ids from database
            List<String> groupIds = getAllGroupsOfUser(sUserId);
            Integer[] asManageableSpaceIds = userManager.getManageableSpaceIds(sUserId, groupIds);

            int parentSpaceId = getDriverSpaceId(sParentSpaceId);

            // retain only sub spaces
            boolean find;
            List<String> manageableRootSpaceIds = new ArrayList<>();
            for (Integer manageableSpaceId : asManageableSpaceIds) {
                find = false;
                Optional<SpaceInstLight> space = treeCache.getSpaceInstLight(manageableSpaceId);
                while (space.isPresent() && !space.get().isRoot() && !find) {
                    int driverFatherId = getDriverSpaceId(space.get().getFatherId());
                    if (parentSpaceId == driverFatherId) {
                        manageableRootSpaceIds.add(String.valueOf(manageableSpaceId));
                        find = true;
                    } else {
                        space = treeCache.getSpaceInstLight(driverFatherId);
                    }
                }
            }
            return manageableRootSpaceIds.toArray(new String[0]);
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting(SUBSPACES_OF_SPACE + sParentSpaceId, "that are manageable by user" + sUserId),
                    e);
        }
    }

    @Override
    public SpaceProfile getSpaceProfile(String spaceId, SilverpeasRole role) throws AdminException {
        SpaceProfile spaceProfile = new SpaceProfile();
        SpaceInst space = getSpaceInstById(spaceId);

        // get profile explicitly defined
        SpaceProfileInst profile = space.getSpaceProfileInst(role.getName());
        if (profile != null) {
            spaceProfile.setProfile(profile);
        }

        if (role == SilverpeasRole.Manager) {
            // get groups and users implicitly inherited from space parents
            boolean root = space.isRoot();
            String parentId = space.getDomainFatherId();
            while (!root) {
                SpaceInst parent = getSpaceInstById(parentId);
                SpaceProfileInst parentProfile = parent.getSpaceProfileInst(role.getName());
                spaceProfile.addInheritedProfile(parentProfile);
                root = parent.isRoot();
                parentId = parent.getDomainFatherId();
            }
        } else {
            // get groups and users from inherited profile
            spaceProfile.addInheritedProfile(space.getInheritedSpaceProfileInst(role.getName()));
        }

        return spaceProfile;
    }

    @Override
    public List<String> getUserManageableGroupIds(String sUserId) throws AdminException {
        try {
            // get all groups of user
            List<String> groupIds = getAllGroupsOfUser(sUserId);

            return groupManager.getManageableGroupIds(sUserId, groupIds);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("groups manageable by user", sUserId), e);
        }
    }

    @Override
    public String[] getAvailCompoIds(String sClientSpaceId, String sUserId) throws AdminException {
        final String[] asAvailCompoIds;

        try {
            // Converts client space id to driver space id
            int spaceId = getDriverSpaceId(sClientSpaceId);

            // Get available component ids from cache
            Optional<String[]> optionalInstanceIds = cache.getAvailCompoIds(spaceId, sUserId);

            if (!optionalInstanceIds.isPresent()) {
                // Get available component ids from database
                asAvailCompoIds = getAvailableInstanceIds(sClientSpaceId, sUserId);
                // Store available component ids in cache
                cache.putAvailCompoIds(String.valueOf(spaceId), sUserId, asAvailCompoIds);
            } else {
                asAvailCompoIds = optionalInstanceIds.get();
            }
            return asAvailCompoIds;

        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting(COMPONENTS_IN_SPACE + sClientSpaceId, AVAILABLE_TO_USER + sUserId), e);
        }
    }

    private String[] getAvailableInstanceIds(final String clientSpaceId, final String userId)
            throws AdminException {
        final List<String> componentIds = new ArrayList<>();
        final List<ComponentInstLight> components = getAvailCompoInSpace(userId, clientSpaceId);
        for (ComponentInstLight component : components) {
            componentIds.add(component.getId());
        }
        return componentIds.toArray(new String[0]);
    }

    @Override
    public boolean isAnAdminTool(String toolId) {
        return Constants.ADMIN_COMPONENT_ID.equals(toolId);
    }

    @Override
    public boolean isComponentAvailable(String componentId, String userId) throws AdminException {
        try {
            return getAllowedComponentIds(userId).contains(componentId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("components available by user", userId), e);
        }
    }

    @Override
    public boolean isComponentManageable(String componentId, String userId) throws AdminException {
        boolean manageable = getUserDetail(userId).isAccessAdmin();
        if (!manageable) {
            // check if user is manager of at least one space parent
            List<String> toCheck = Arrays.asList(getUserManageableSpaceIds(userId));
            List<SpaceInstLight> path = getPathToComponent(componentId);
            for (SpaceInstLight space : path) {
                if (toCheck.contains(String.valueOf(space.getLocalId()))) {
                    manageable = true;
                    break;
                }
            }
        }
        return manageable;
    }

    @Override
    public String[] getAvailCompoIdsAtRoot(String sClientSpaceId, String sUserId) throws AdminException {
        try {
            // Converts client space id to driver space id
            int spaceId = getDriverSpaceId(sClientSpaceId);
            List<String> groupIds = getAllGroupsOfUser(sUserId);
            List<String> asAvailCompoIds = componentManager.getAllowedComponentIds(Integer.parseInt(sUserId),
                    groupIds, spaceId);

            return asAvailCompoIds.toArray(new String[asAvailCompoIds.size()]);
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting("root components in space " + sClientSpaceId, AVAILABLE_TO_USER + sUserId), e);
        }
    }

    @Override
    public List<String> getAvailCompoIdsAtRoot(String sClientSpaceId, String sUserId, String componentNameRoot)
            throws AdminException {

        try {
            // Converts client space id to driver space id
            int spaceId = getDriverSpaceId(sClientSpaceId);

            // Get available component ids from database
            List<ComponentInstLight> components = treeCache.getComponents(spaceId);

            List<String> allowedComponentIds = getAllowedComponentIds(sUserId);
            List<String> result = new ArrayList<>();
            for (ComponentInstLight component : components) {
                if (allowedComponentIds.contains(component.getId())
                        && component.getName().startsWith(componentNameRoot)) {
                    result.add(component.getId());
                }
            }

            return result;
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting("root components in space " + sClientSpaceId, AVAILABLE_TO_USER + sUserId), e);
        }
    }

    @Override
    public String[] getAvailCompoIds(String userId) throws AdminException {
        try {
            List<String> componentIds = getAllowedComponentIds(userId);

            return componentIds.toArray(new String[componentIds.size()]);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("components available to user", userId), e);
        }
    }

    @Override
    public String[] getAvailDriverCompoIds(String sClientSpaceId, String sUserId) throws AdminException {
        try {
            // Get available component ids
            return getAvailableInstanceIds(sClientSpaceId, sUserId);
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting(COMPONENTS_IN_SPACE + sClientSpaceId, AVAILABLE_TO_USER + sUserId), e);
        }
    }

    @Override
    public String[] getComponentIdsByNameAndUserId(String sUserId, String sComponentName) throws AdminException {

        List<String> allowedComponentIds = getAllowedComponentIds(sUserId, sComponentName);
        return allowedComponentIds.toArray(new String[allowedComponentIds.size()]);
    }

    @Override
    public List<ComponentInstLight> getAvailComponentInstLights(String userId, String componentName)
            throws AdminException {

        List<ComponentInstLight> components = new ArrayList<>();
        List<String> allowedComponentIds = getAllowedComponentIds(userId, componentName);

        for (String allowedComponentId : allowedComponentIds) {
            ComponentInstLight componentInst = getComponentInstLight(allowedComponentId);

            if (componentInst.getName().equalsIgnoreCase(componentName)) {
                components.add(componentInst);
            }
        }
        return components;
    }

    @Override
    public List<SpaceInstLight> getRootSpacesContainingComponent(String userId, String componentName)
            throws AdminException {
        List<SpaceInstLight> spaces = new ArrayList<>();
        List<ComponentInstLight> components = getAvailComponentInstLights(userId, componentName);
        for (ComponentInstLight component : components) {
            List<SpaceInstLight> path = treeCache.getComponentPath(component.getId());
            if (!path.isEmpty()) {
                SpaceInstLight root = path.get(0);
                if (!spaces.contains(root)) {
                    spaces.add(root);
                }
            }
        }
        return spaces;
    }

    @Override
    public List<SpaceInstLight> getSubSpacesContainingComponent(String spaceId, String userId, String componentName)
            throws AdminException {
        List<SpaceInstLight> spaces = new ArrayList<>();
        int driverSpaceId = getDriverSpaceId(spaceId);
        List<ComponentInstLight> components = getAvailComponentInstLights(userId, componentName);

        for (ComponentInstLight component : components) {
            List<SpaceInstLight> path = treeCache.getComponentPath(component.getId());
            for (SpaceInstLight space : path) {
                if (getDriverSpaceId(space.getFatherId()) == driverSpaceId && !spaces.contains(space)) {
                    spaces.add(space);
                }
            }
        }
        return spaces;
    }

    @Override
    public CompoSpace[] getCompoForUser(String sUserId, String sComponentName) throws AdminException {
        ArrayList<CompoSpace> alCompoSpace = new ArrayList<>();

        try {
            List<ComponentInstLight> components = getAvailComponentInstLights(sUserId, sComponentName);
            for (ComponentInstLight componentInst : components) {
                // Create new instance of CompoSpace
                CompoSpace compoSpace = new CompoSpace();
                // Set the component Id
                compoSpace.setComponentId(componentInst.getId());
                // Set the component label
                if (StringUtil.isDefined(componentInst.getLabel())) {
                    compoSpace.setComponentLabel(componentInst.getLabel());
                } else {
                    compoSpace.setComponentLabel(componentInst.getName());
                }

                // Set the space label
                compoSpace.setSpaceId(getClientSpaceId(componentInst.getDomainFatherId()));

                SpaceInstLight spaceInst = getSpaceInstLightById(componentInst.getDomainFatherId());
                compoSpace.setSpaceLabel(spaceInst.getName());

                alCompoSpace.add(compoSpace);
            }

            return alCompoSpace.toArray(new CompoSpace[alCompoSpace.size()]);
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting("instances of component " + sComponentName, AVAILABLE_TO_USER + sUserId), e);
        }
    }

    @Override
    public String[] getCompoId(String sComponentName) throws AdminException {
        try {
            // Build the list of instanciated components with given componentName
            String[] matchingComponentIds = componentManager.getAllCompoIdsByComponentName(sComponentName);

            // check TreeCache to know if component is not removed neither into a removed space
            List<String> shortIds = new ArrayList<>();
            for (String componentId : matchingComponentIds) {
                Optional<ComponentInstLight> component = treeCache.getComponent(sComponentName + componentId);
                component.ifPresent(c -> shortIds.add(componentId));
            }
            return shortIds.toArray(new String[0]);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("instances of component", sComponentName), e);
        }
    }

    @Override
    public List<ComponentInstLight> getComponentsWithParameter(String paramName, String paramValue) {
        try {
            final Parameter param = new Parameter();
            param.setName(paramName);
            param.setValue(paramValue);
            final List<Integer> componentIds = componentManager.getComponentIds(param);
            final List<ComponentInstLight> components = new ArrayList<>();
            for (Integer id : componentIds) {
                ComponentInst component = getComponentInst(id, null);
                // check TreeCache to know if component is not removed neither into a removed space
                Optional<ComponentInstLight> componentLight = treeCache.getComponent(component.getId());
                componentLight.filter(c -> !c.isRemoved()).ifPresent(components::add);
            }
            return components;
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
            return Collections.emptyList();
        }
    }

    @Override
    public String[] getProfileIds(String sUserId) throws AdminException {
        try {
            final String[] asProfilesIds;
            // Get the profile ids from cache
            Optional<String[]> optionalProfileIds = cache.getProfileIds(sUserId);
            if (!optionalProfileIds.isPresent()) {
                // retrieve value from database
                asProfilesIds = profileManager.getProfileIdsOfUser(sUserId, getAllGroupsOfUser(sUserId));
                // store values in cache
                if (asProfilesIds != null) {
                    cache.putProfileIds(sUserId, asProfilesIds);
                }
            } else {
                asProfilesIds = optionalProfileIds.get();
            }

            return asProfilesIds;
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("profiles of user", sUserId), e);
        }
    }

    @Override
    public String[] getProfileIdsOfGroup(String sGroupId) throws AdminException {
        return getDirectComponentProfileIdsOfGroup(sGroupId);
    }

    @Override
    public String[] getCurrentProfiles(String sUserId, ComponentInst componentInst) {
        ArrayList<String> alProfiles = new ArrayList<>();

        try {
            // Build the list of profiles containing the given user
            String[] asProfileIds = getProfileIds(sUserId);

            for (String asProfileId : asProfileIds) {
                for (int nJ = 0; nJ < componentInst.getNumProfileInst(); nJ++) {
                    if (componentInst.getProfileInst(nJ).getId().equals(asProfileId)) {
                        alProfiles.add(componentInst.getProfileInst(nJ).getName());
                    }
                }
            }

            return arrayListToString(removeTuples(alProfiles));
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }
    }

    @Override
    public String[] getCurrentProfiles(String sUserId, String componentId) throws AdminException {
        return profileManager.getProfileNamesOfUser(sUserId, getAllGroupsOfUser(sUserId),
                getDriverComponentId(componentId));
    }

    @Override
    public UserDetail[] getUsers(boolean bAllProfiles, String sProfile, String sClientSpaceId,
            String sClientComponentId) throws AdminException {
        ArrayList<String> alUserIds = new ArrayList<>();

        try {
            ComponentInst componentInst = getComponentInst(getDriverComponentId(sClientComponentId),
                    getDriverSpaceId(sClientSpaceId));
            componentInst = checkComponentInstanceById(componentInst, sClientComponentId,
                    nullComponentInstSupplier);
            Objects.requireNonNull(componentInst);

            for (ProfileInst profile : componentInst.getAllProfilesInst()) {
                if (profile != null && (profile.getName().equals(sProfile) || bAllProfiles)) {
                    // add direct users
                    alUserIds.addAll(profile.getAllUsers());
                    // add users of groups
                    addUsersOfAllGroups(alUserIds, profile);
                }
            }

            removeTuples(alUserIds);

            // Get the users details
            UserDetail[] userDetails = new UserDetail[alUserIds.size()];
            for (int nI = 0; nI < userDetails.length; nI++) {
                userDetails[nI] = getUserDetail(alUserIds.get(nI));
            }

            return userDetails;
        } catch (Exception e) {
            throw new AdminException(
                    failureOnGetting("users with profile " + sProfile, "of the component " + sClientComponentId),
                    e);
        }
    }

    private void addUsersOfAllGroups(final ArrayList<String> alUserIds, final ProfileInst profile)
            throws AdminException {
        List<String> groupIds = profile.getAllGroups();
        for (String groupId : groupIds) {
            List<String> subGroupIds = groupManager.getAllSubGroupIdsRecursively(groupId);
            // add current group
            subGroupIds.add(groupId);
            if (!subGroupIds.isEmpty()) {
                UserDetail[] users = userManager.getAllUsersInGroups(subGroupIds);
                for (UserDetail user : users) {
                    alUserIds.add(user.getId());
                }
            }
        }
    }

    @Override
    public GroupDetail[] getAllSubGroups(String parentGroupId) throws AdminException {
        List<GroupDetail> subgroups = groupManager.getSubGroups(parentGroupId);
        return subgroups.toArray(new GroupDetail[subgroups.size()]);
    }

    @Override
    public GroupDetail[] getRecursivelyAllSubGroups(String parentGroupId) throws AdminException {
        List<GroupDetail> subgroups = groupManager.getRecursivelySubGroups(parentGroupId);
        return subgroups.toArray(new GroupDetail[subgroups.size()]);
    }

    @Override
    public UserDetail[] getFiltredDirectUsers(String sGroupId, String sUserLastNameFilter) throws AdminException {
        GroupDetail theGroup = getGroup(sGroupId);

        if (theGroup == null) {
            return new UserDetail[0];
        }
        String[] usersIds = theGroup.getUserIds();
        if (usersIds == null || usersIds.length <= 0) {
            return new UserDetail[0];
        }
        if (sUserLastNameFilter == null || sUserLastNameFilter.length() <= 0) {
            return getUserDetails(usersIds);
        }
        String upperFilter = sUserLastNameFilter.toUpperCase();
        ArrayList<UserDetail> matchedUsers = new ArrayList<>();
        for (final String usersId : usersIds) {
            UserDetail currentUser = getUserDetail(usersId);
            if (currentUser != null && currentUser.getLastName().toUpperCase().startsWith(upperFilter)) {
                matchedUsers.add(currentUser);
            }
        }
        return matchedUsers.toArray(new UserDetail[matchedUsers.size()]);
    }

    @Override
    public int getAllSubUsersNumber(String sGroupId) throws AdminException {
        if (!StringUtil.isDefined(sGroupId)) {
            return userManager.getUserCount();
        } else {

            return groupManager.getTotalUserCountInGroup("", sGroupId);
        }
    }

    @Override
    public int getUsersNumberOfDomain(String domainId) throws AdminException {
        try {
            if (!StringUtil.isDefined(domainId)) {
                return userManager.getUserCount();
            }
            if ("-1".equals(domainId)) {
                return 0;
            }
            return userManager.getNumberOfUsersInDomain(domainId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("user count in domain", domainId), e);
        }
    }

    @Override
    public String[] getAdministratorUserIds(String fromUserId) throws AdminException {
        return userManager.getAllAdminIds(getUserDetail(fromUserId));
    }

    @Override
    public String getSilverpeasEmail() {
        return senderEmail;
    }

    @Override
    public String getSilverpeasName() {
        return senderName;
    }

    @Override
    public String getDAPIGeneralAdminId() {
        return "0";
    }

    // -------------------------------------------------------------------------
    // UTILS
    // -------------------------------------------------------------------------
    private String[] arrayListToString(ArrayList<String> al) {
        if (al == null) {
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }

        String[] as = new String[al.size()];
        for (int nI = 0; nI < al.size(); nI++) {
            as[nI] = al.get(nI);
        }

        return as;
    }

    private ArrayList<String> removeTuples(ArrayList<String> al) {
        if (al == null) {
            return new ArrayList<>();
        }

        for (int nI = 0; nI < al.size(); nI++) {
            while (al.lastIndexOf(al.get(nI)) != al.indexOf(al.get(nI))) {
                al.remove(al.lastIndexOf(al.get(nI)));
            }
        }

        return al;
    }

    // -------------------------------------------------------------------
    // RE-INDEXATION
    // -------------------------------------------------------------------
    @Override
    public String[] getAllSpaceIds(String sUserId) throws AdminException {
        return getClientSpaceIds(getUserSpaceIds(sUserId));
    }

    @Override
    public String[] getAllRootSpaceIds(String sUserId) throws AdminException {
        return getClientSpaceIds(getUserRootSpaceIds(sUserId));
    }

    @Override
    public String[] getAllSubSpaceIds(String sSpaceId, String sUserId) throws AdminException {
        return getUserSubSpaceIds(sUserId, sSpaceId);
    }

    @Override
    public String[] getAllComponentIds(String sSpaceId) throws AdminException {
        List<String> alCompoIds = new ArrayList<>();

        // Get the compo of this space
        SpaceInst spaceInst = getSpaceInstById(sSpaceId);
        List<SilverpeasComponentInstance> alCompoInst = spaceInst.getAllComponentInstances();

        if (alCompoInst != null) {
            for (SilverpeasComponentInstance anAlCompoInst : alCompoInst) {
                alCompoIds.add(anAlCompoInst.getId());
            }
        }

        return alCompoIds.toArray(new String[alCompoIds.size()]);
    }

    @Override
    public String[] getAllComponentIdsRecur(String sSpaceId) throws AdminException {
        List<ComponentInstLight> components = treeCache
                .getComponentsInSpaceAndSubspaces(getDriverSpaceId(sSpaceId));

        List<String> componentIds = new ArrayList<>();
        for (ComponentInstLight component : components) {
            componentIds.add(component.getId());
        }

        final SpaceInst space = getSpaceInstById(sSpaceId);
        if (space.isPersonalSpace()) {
            PersonalComponent.getAll()
                    .forEach(p -> componentIds.add(PersonalComponentInstance.from(space.getCreator(), p).getId()));
        }

        return componentIds.toArray(new String[componentIds.size()]);
    }

    @Override
    public String[] getAllComponentIdsRecur(String sSpaceId, String sUserId, String componentNameRoot,
            boolean inCurrentSpace, boolean inAllSpaces) throws AdminException {
        ArrayList<String> alCompoIds = new ArrayList<>();
        // In All silverpeas
        if (inAllSpaces) {
            CompoSpace[] cs = getCompoForUser(sUserId, componentNameRoot);
            for (CompoSpace c : cs) {
                alCompoIds.add(c.getComponentId());
            }
        } else {
            alCompoIds = getAllComponentIdsRecur(sSpaceId, sUserId, componentNameRoot, inCurrentSpace);
        }
        return arrayListToString(alCompoIds);
    }

    private ArrayList<String> getAllComponentIdsRecur(String sSpaceId, String sUserId, String componentNameRoot,
            boolean inCurrentSpace) throws AdminException {
        ArrayList<String> alCompoIds = new ArrayList<>();
        getComponentIdsByNameAndUserId(sUserId, componentNameRoot);
        // Get components in the root of the space
        if (inCurrentSpace) {
            String[] componentIds = getAvailCompoIdsAtRoot(sSpaceId, sUserId);
            addComponentIdsMatchingName(componentNameRoot, componentIds, alCompoIds);
        }

        // Get components in sub spaces
        String[] asSubSpaceIds = getAllSubSpaceIds(sSpaceId);
        for (int nI = 0; asSubSpaceIds != null && nI < asSubSpaceIds.length; nI++) {

            SpaceInst spaceInst = getSpaceInstById(asSubSpaceIds[nI]);
            String[] componentIds = getAvailCompoIds(spaceInst.getId(), sUserId);

            addComponentIdsMatchingName(componentNameRoot, componentIds, alCompoIds);
        }
        return alCompoIds;
    }

    private void addComponentIdsMatchingName(final String componentNameRoot, final String[] componentIds,
            final ArrayList<String> alCompoIds) throws AdminException {
        if (componentIds != null) {
            for (String componentId : componentIds) {
                ComponentInstLight compo = getComponentInstLight(componentId);
                if (compo.getName().equals(componentNameRoot)) {
                    alCompoIds.add(compo.getId());
                }
            }
        }
    }

    @Override
    public void synchronizeGroupByRule(String groupId, boolean scheduledMode) throws AdminException {
        GroupDetail group = getGroup(groupId);
        String rule = group.getRule();
        if (StringUtil.isDefined(rule)) {
            try {
                if (!scheduledMode) {
                    SynchroGroupReport.setReportLevel(Level.DEBUG);
                    SynchroGroupReport.startSynchro();
                }
                SynchroGroupReport.warn(ADMIN_SYNCHRONIZE_GROUP, "Synchronisation du groupe '" + group.getName()
                        + "' - Regle de synchronisation = \"" + rule + "\"");
                List<String> actualUserIds = Arrays.asList(group.getUserIds());
                // Getting users according to rule
                List<String> userIds = GroupSynchronizationRule.from(group).getUserIds();

                // Add users
                List<String> newUsers = new ArrayList<>();
                if (userIds != null) {
                    synchroAddUsersToAdd(actualUserIds, userIds, newUsers);
                }
                SynchroGroupReport.warn(ADMIN_SYNCHRONIZE_GROUP, "Ajout de " + newUsers.size() + " utilisateur(s)");
                if (!newUsers.isEmpty()) {
                    SynchroGroupReport.debug("admin.synchronizeGroup()",
                            () -> "Ajout de l'utilisateur d'ID "
                                    + newUsers.stream().collect(Collectors.joining(", ")) + " dans le groupe d'ID "
                                    + groupId);
                    groupManager.addUsersInGroup(newUsers, groupId);
                }

                // Remove users
                List<String> removedUsers = new ArrayList<>();
                synchroAddUsersToRemove(actualUserIds, userIds, removedUsers);
                SynchroGroupReport.warn(ADMIN_SYNCHRONIZE_GROUP,
                        REMOVE_OF + removedUsers.size() + " utilisateur(s)");
                if (!removedUsers.isEmpty()) {
                    groupManager.removeUsersFromGroup(removedUsers, groupId);
                }
            } catch (Exception e) {
                SynchroGroupReport.error(ADMIN_SYNCHRONIZE_GROUP,
                        "Error during the processing of synchronization rule of group '" + groupId + "': "
                                + e.getMessage(),
                        null);
                throw new AdminException("Fail to synchronize group " + groupId, e);
            } finally {
                if (!scheduledMode) {
                    SynchroGroupReport.stopSynchro();
                }
            }
        }
    }

    private void synchroAddUsersToRemove(final List<String> actualUserIds, final List<String> userIds,
            final List<String> removedUsers) {
        for (String actualUserId : actualUserIds) {
            if (userIds == null || !userIds.contains(actualUserId)) {
                removedUsers.add(actualUserId);
                SynchroGroupReport.info(ADMIN_SYNCHRONIZE_GROUP, "Suppression de l'utilisateur " + actualUserId);
            }
        }
    }

    private void synchroAddUsersToAdd(final List<String> actualUserIds, final List<String> userIds,
            final List<String> newUsers) {
        for (String userId : userIds) {
            if (!actualUserIds.contains(userId)) {
                newUsers.add(userId);
                SynchroGroupReport.info(ADMIN_SYNCHRONIZE_GROUP, "Ajout de l'utilisateur " + userId);
            }
        }
    }

    // //////////////////////////////////////////////////////////
    // Synchronization tools
    // //////////////////////////////////////////////////////////
    private List<String> translateGroupIds(String sDomainId, String[] groupSpecificIds, boolean recursGroups) {
        List<String> convertedGroupIds = new ArrayList<>();
        String groupId;
        for (String groupSpecificId : groupSpecificIds) {
            try {
                groupId = groupManager.getGroupIdBySpecificIdAndDomainId(groupSpecificId, sDomainId);
            } catch (AdminException e) {
                // The group doesn't exist -> Synchronize him
                groupId = null;
                SilverLogger.getLogger(this).warn("Group {0} not found. Synchronize it", groupSpecificId);
                if (recursGroups) {
                    try {
                        groupId = synchronizeImportGroup(sDomainId, groupSpecificId, null, true, true);
                    } catch (AdminException ex) {
                        // The group's synchro failed -> ignore him
                        SilverLogger.getLogger(this).error(ex);
                        groupId = null;
                    }
                }
            }

            if (groupId != null) {
                convertedGroupIds.add(groupId);
            }
        }
        return convertedGroupIds;
    }

    private String[] translateUserIds(String sDomainId, String[] userSpecificIds) {
        List<String> convertedUserIds = new ArrayList<>();
        String userId = null;
        for (String userSpecificId : userSpecificIds) {
            try {
                userId = userManager.getUserIdBySpecificIdAndDomainId(userSpecificId, sDomainId);
                if (userId == null) {
                    // The user doesn't exist -> Synchronize him
                    SilverLogger.getLogger(this).warn("The user {0} doesn't exist. Synchronize it", userSpecificId);
                    userId = synchronizeImportUser(sDomainId, userSpecificId, false);
                }
            } catch (AdminException e) {
                // The user's synchro failed -> Ignore him
                SilverLogger.getLogger(this).error(e);
            }
            if (userId != null) {
                convertedUserIds.add(userId);
            }
        }
        return convertedUserIds.toArray(new String[0]);
    }

    @Override
    public String synchronizeGroup(String groupId, boolean recurs) throws AdminException {

        GroupDetail theGroup = getGroup(groupId);
        if (theGroup.isSynchronized()) {
            synchronizeGroupByRule(groupId, false);
        } else {
            DomainDriver synchroDomain = domainDriverManager.getDomainDriver(theGroup.getDomainId());
            GroupDetail gr = synchroDomain.synchroGroup(theGroup.getSpecificId());

            gr.setId(groupId);
            gr.setDomainId(theGroup.getDomainId());
            gr.setSuperGroupId(theGroup.getSuperGroupId());
            internalSynchronizeGroup(synchroDomain, gr, recurs);
        }
        return groupId;
    }

    @Override
    public String synchronizeImportGroup(String domainId, String groupKey, String askedParentId, boolean recurs,
            boolean isIdKey) throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        GroupDetail gr;

        if (isIdKey) {
            gr = synchroDomain.synchroGroup(groupKey);
        } else {
            gr = synchroDomain.importGroup(groupKey);
        }
        gr.setDomainId(domainId);

        // We now search for the parent of this group
        // ------------------------------------------
        // First, we get the parents of the group
        String[] parentSpecificIds = synchroDomain.getGroupMemberGroupIds(gr.getSpecificId());
        String parentId = null;
        for (int i = 0; i < parentSpecificIds.length && parentId == null; i++) {
            try {
                parentId = groupManager.getGroupIdBySpecificIdAndDomainId(parentSpecificIds[i], domainId);
                if (askedParentId != null && !askedParentId.isEmpty() && !askedParentId.equals(parentId)) {
                    // It is not the matching parent
                    parentId = null;
                }
            } catch (AdminException e) {
                // The user doesn't exist -> Synchronize him
                parentId = null;
            }
        }
        if (parentId == null
                && (parentSpecificIds.length > 0 || (askedParentId != null && askedParentId.length() > 0))) {// We
            // can't
            // add
            // the
            // group
            // (just
            // the
            // same
            // restriction as for the directories...)
            throw new AdminException("Fail to synchronize imported group " + groupKey + " in domain " + domainId);
        }
        // The group is a root group or have a known parent
        gr.setSuperGroupId(parentId);

        // We must first add the group with no child. Then, the childs will be added
        // during the internal synchronization function call
        String[] specificIds = gr.getUserIds();
        gr.setUserIds(ArrayUtil.EMPTY_STRING_ARRAY);
        String groupId = addGroup(gr, true);
        gr.setId(groupId);
        gr.setUserIds(specificIds);
        internalSynchronizeGroup(synchroDomain, gr, recurs);
        return groupId;
    }

    @Override
    public String synchronizeRemoveGroup(String groupId) throws AdminException {
        GroupDetail theGroup = getGroup(groupId);
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(theGroup.getDomainId());
        synchroDomain.removeGroup(theGroup.getSpecificId());
        return deleteGroupById(groupId, true);
    }

    protected void internalSynchronizeGroup(DomainDriver synchroDomain, GroupDetail latestGroup, boolean recurs)
            throws AdminException {
        latestGroup.setUserIds(translateUserIds(latestGroup.getDomainId(), latestGroup.getUserIds()));
        updateGroup(latestGroup, true);
        if (recurs) {
            GroupDetail[] childs = synchroDomain.getGroups(latestGroup.getSpecificId());

            for (final GroupDetail child : childs) {
                String existingGroupId = null;
                try {
                    existingGroupId = groupManager.getGroupIdBySpecificIdAndDomainId(child.getSpecificId(),
                            latestGroup.getDomainId());
                    GroupDetail existingGroup = getGroup(existingGroupId);
                    if (existingGroup.getSuperGroupId().equals(latestGroup.getId())) {
                        // Only synchronize the group if latestGroup is his true parent
                        synchronizeGroup(existingGroupId, recurs);
                    }
                } catch (AdminException e) {
                    // The group doesn't exist -> Import him
                    if (existingGroupId == null) { // Import the new group
                        synchronizeImportGroup(latestGroup.getDomainId(), child.getSpecificId(),
                                latestGroup.getId(), recurs, true);
                    }
                }
            }
        }
    }

    @Override
    public String synchronizeUser(String userId, boolean recurs) throws AdminException {
        Collection<UserDetail> listUsersUpdate = new ArrayList<>();
        try {
            UserDetail theUserDetail = getUserDetail(userId);
            DomainDriver synchroDomain = domainDriverManager.getDomainDriver(theUserDetail.getDomainId());
            // Synchronize the user's infos
            UserDetail ud = synchroDomain.synchroUser(theUserDetail.getSpecificId());
            ud.setId(userId);
            ud.setAccessLevel(theUserDetail.getAccessLevel());
            ud.setDomainId(theUserDetail.getDomainId());
            if (!ud.equals(theUserDetail)
                    || (ud.getState() != UserState.UNKNOWN && ud.getState() != theUserDetail.getState())) {
                mergeDistantUserIntoSilverpeasUser(ud, theUserDetail);
                userManager.updateUser(theUserDetail, true);
                cache.opUpdateUser(userManager.getUserDetail(userId));
            }
            // Synchro manuelle : Ajoute ou Met  jour l'utilisateur
            listUsersUpdate.add(ud);

            // Synchronize the user's groups
            String[] incGroupsSpecificId = synchroDomain.getUserMemberGroupIds(theUserDetail.getSpecificId());
            List<String> incGroupsId = translateGroupIds(theUserDetail.getDomainId(), incGroupsSpecificId, recurs);
            List<GroupDetail> oldGroups = groupManager.getDirectGroupsOfUser(userId);
            for (GroupDetail oldGroup : oldGroups) {
                if (incGroupsId.contains(oldGroup.getId())) { // No changes have to be
                    // performed to the group -> Remove it
                    incGroupsId.remove(oldGroup.getId());
                } else {
                    if (theUserDetail.getDomainId().equals(oldGroup.getDomainId())) {
                        // Remove the user from this group
                        groupManager.removeUserFromGroup(userId, oldGroup.getId());
                        cache.opRemoveUserFromGroup(userId);
                    }
                }
            }
            // Now the remaining groups of the vector are the groups where the user is
            // newly added
            for (String includedGroupId : incGroupsId) {
                groupManager.addUserInGroup(userId, includedGroupId);
                cache.opAddUserInGroup(userId);
            }

            // traitement spcifique des users selon l'interface implmente
            processSpecificSynchronization(theUserDetail.getDomainId(), null, listUsersUpdate, null);

            // return user id
            return userId;
        } catch (Exception e) {
            throw new AdminException("Fail to synchronize user " + userId, e);
        }
    }

    @Override
    public String synchronizeImportUserByLogin(String domainId, String userLogin, boolean recurs)
            throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        UserDetail ud = synchroDomain.importUser(userLogin);
        ud.setDomainId(domainId);
        String userId = addUser(ud, true);
        // Synchronizes the user to add it to the groups and recursivaly add the groups
        synchronizeUser(userId, recurs);
        return userId;
    }

    @Override
    public String synchronizeImportUser(String domainId, String specificId, boolean recurs) throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        UserDetail ud = synchroDomain.getUser(specificId);

        ud.setDomainId(domainId);
        String userId = addUser(ud, true);
        // Synchronizes the user to add it to the groups and recursivaly add the groups
        synchronizeUser(userId, recurs);
        return userId;
    }

    @Override
    public List<DomainProperty> getSpecificPropertiesToImportUsers(String domainId, String language)
            throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        return synchroDomain.getPropertiesToImport(language);
    }

    @Override
    public UserDetail[] searchUsers(String domainId, Map<String, String> query) throws AdminException {
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(domainId);
        return synchroDomain.getUsersByQuery(query);
    }

    @Override
    public String synchronizeRemoveUser(String userId) throws AdminException {
        UserDetail theUserDetail = getUserDetail(userId);
        DomainDriver synchroDomain = domainDriverManager.getDomainDriver(theUserDetail.getDomainId());
        synchroDomain.removeUser(theUserDetail.getSpecificId());
        removeUser(userId);
        processSpecificSynchronization(theUserDetail.getDomainId(), null, null, singletonList(theUserDetail));
        return userId;
    }

    @Override
    public String synchronizeSilverpeasWithDomain(String sDomainId) throws AdminException {
        return synchronizeSilverpeasWithDomain(sDomainId, false);
    }

    @Override
    public String synchronizeSilverpeasWithDomain(String sDomainId, boolean threaded) throws AdminException {
        try {
            final Pair<String, List<AbstractBackgroundProcessRequest>> result = Transaction
                    .performInNew(new Process<Pair<String, List<AbstractBackgroundProcessRequest>>>() {
                        @Override
                        public Pair<String, List<AbstractBackgroundProcessRequest>> execute() throws Exception {
                            String sReport = "Starting synchronization...\n\n";
                            synchronized (semaphore) {
                                // Starting synchronization with a status popup
                                SynchroDomainReport.startSynchro();
                                try {
                                    SynchroDomainReport.info(ADMIN_SYNCHRONIZE_DOMAIN,
                                            "Domain '" + domainDriverManager.getDomain(sDomainId).getName()
                                                    + "', Id : " + sDomainId);
                                    // Start synchronization
                                    domainDriverManager.beginSynchronization(sDomainId);
                                    final DomainDriver synchroDomain = domainDriverManager
                                            .getDomainDriver(sDomainId);
                                    // Synchronize users
                                    final boolean addUserIntoSilverpeas = synchroDomain.mustImportUsers()
                                            || threaded;
                                    final SyncOfUsersContext context = new SyncOfUsersContext(sDomainId, threaded,
                                            addUserIntoSilverpeas, delUsersOnDiffSynchro);
                                    final SyncOfUsersContext syncOfUsersContext = synchronizeUsers(context);
                                    sReport += syncOfUsersContext.getReport();
                                    // Synchronize groups
                                    // Get all users of the domain from Silverpeas
                                    final UserDetail[] silverpeasUDs = userManager.getAllUsersInDomain(sDomainId,
                                            true);
                                    final Map<String, String> userIdsMapping = getUserIdsMapping(silverpeasUDs);
                                    sReport += "\n" + synchronizeGroups(sDomainId, userIdsMapping);
                                    // End synchronization
                                    final String sDomainSpecificErrors = domainDriverManager
                                            .endSynchronization(sDomainId, false);
                                    if (StringUtil.isDefined(sDomainSpecificErrors)) {
                                        SynchroDomainReport.info(ADMIN_SYNCHRONIZE_DOMAIN,
                                                "----------------" + sDomainSpecificErrors);
                                    }
                                    return Pair.of(sReport + "\n----------------\n" + sDomainSpecificErrors,
                                            singletonList(syncOfUsersContext.getIndexationBackgroundProcess()));
                                } catch (Exception e) {
                                    try {
                                        // End synchronization
                                        domainDriverManager.endSynchronization(sDomainId, true);
                                    } catch (Exception e1) {
                                        SilverLogger.getLogger(this).error(e1);
                                    }
                                    SynchroDomainReport.error(ADMIN_SYNCHRONIZE_DOMAIN,
                                            "Problme lors de la synchronisation : " + e.getMessage(), null);
                                    throw new AdminException(
                                            "Fail to synchronize domain " + sDomainId + ". Report: " + sReport, e);
                                } finally {
                                    SynchroDomainReport.stopSynchro();// Fin de synchro avec la Popup d'affichage
                                    // Reset the cache
                                    cache.resetCache();
                                }
                            }
                        }
                    });
            result.getSecond().forEach(BackgroundProcessTask::push);
            return result.getFirst();
        } catch (Exception e) {
            if (e.getCause() instanceof AdminException) {
                throw e;
            }
            throw new AdminException(e);
        }
    }

    /**
     * Merge the data of a distant user into the data of a silverpeas user : - user identifier (the
     * distant one) - first name - last name - e-mail - login
     * @param distantUser {@link UserDetail} representing data on externam repository.
     * @param silverpeasUser {@link UserDetail} representing data on silverpeas.
     * @return true if a data has changed, false otherwise.
     */
    static boolean mergeDistantUserIntoSilverpeasUser(final UserDetail distantUser,
            final UserDetail silverpeasUser) {
        boolean dataUpdated = !Objects.equals(silverpeasUser.getSpecificId(), distantUser.getSpecificId());
        silverpeasUser.setSpecificId(distantUser.getSpecificId());
        dataUpdated |= !Objects.equals(silverpeasUser.getFirstName(), distantUser.getFirstName());
        silverpeasUser.setFirstName(distantUser.getFirstName());
        dataUpdated |= !Objects.equals(silverpeasUser.getLastName(), distantUser.getLastName());
        silverpeasUser.setLastName(distantUser.getLastName());
        dataUpdated |= !Objects.equals(silverpeasUser.geteMail(), distantUser.geteMail());
        silverpeasUser.seteMail(distantUser.geteMail());
        dataUpdated |= !Objects.equals(silverpeasUser.getLogin(), distantUser.getLogin());
        silverpeasUser.setLogin(distantUser.getLogin());
        if (silverpeasUser.isRemovedState()) {
            return dataUpdated;
        }
        if (distantUser.isDeactivatedState()
                || (distantUser.isValidState() && silverpeasUser.isDeactivatedState())) {
            // The user account is deactivated from the LDAP
            // or
            // The user account is activated from the LDAP, so the Silverpeas user
            // account is again activated only if it was deactivated. Indeed, if it was blocked
            // for example, it is still blocked after a synchronization
            dataUpdated |= !Objects.equals(silverpeasUser.getState(), distantUser.getState());
            silverpeasUser.setState(distantUser.getState());
        }
        return dataUpdated;
    }

    /**
     * Synchronize users between cache and domain's datasource
     */
    private SyncOfUsersContext synchronizeUsers(final SyncOfUsersContext context) throws AdminException {
        final String domainId = context.getDomainId();
        context.appendToReport("User synchronization : \n");
        String message;
        SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, "Starting synchronization of users...");
        final UserDetail[] distantUDs = domainDriverManager.getAllUsers(context.getDomainId());
        SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_USERS, format(
                "Existing currently {0} users in external repository before synchronization", distantUDs.length));
        final UserDetail[] silverpeasUDs = userManager.getAllUsersInDomain(domainId, true);
        SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_USERS,
                format("Existing currently {0} users in Silverpeas before synchronization", silverpeasUDs.length));
        try {
            performRemoveOfUsersDuringSynchronization(context, distantUDs, silverpeasUDs);
            performSaveOfUsersDuringSynchronization(context, distantUDs, silverpeasUDs);
            processSpecificSynchronization(domainId, context.getAddedUsers().values(),
                    context.getUpdatedUsers().values(), context.getRemovedUsers().values());
            message = "Synchronization of users terminated";
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
            message = "# of updated users: " + context.getUpdatedUsers().size() + ", added: "
                    + context.getAddedUsers().size() + ", removed: " + context.getRemovedUsers().size()
                    + ", restored: " + context.getRestoredUsers().size() + ", deleted: "
                    + context.getDeletedUsers().size();
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
            context.setIndexationBackgroundProcess(
                    new BackgroundUserIndexationProcess(domainDriverManager, context));
            return context;
        } catch (Exception e) {
            SynchroDomainReport.error(ADMIN_SYNCHRONIZE_USERS,
                    "Problem during synchronization of users : " + e.getMessage(), null);
            throw new AdminException("Fail to synchronize domain " + domainId + ". Report: " + context.getReport(),
                    e);
        }
    }

    private void performRemoveOfUsersDuringSynchronization(final SyncOfUsersContext context,
            final UserDetail[] distantUDs, final UserDetail[] silverpeasUDs) {
        if (context.isRemoveOperationToPerform()) {
            SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_USERS, "Removing users from database...");
            final Set<String> indexedDistantUsers = extractUserSpecificIdAndFallbackLogin(distantUDs);
            for (UserDetail silverpeasUD : silverpeasUDs) {
                // search for user in distant datasource
                if (!silverpeasUD.isRemovedState()
                        && !existsUserBySpecificIdOrFallbackLoginIn(silverpeasUD, indexedDistantUsers)) {
                    removeUserDuringSynchronization(context, silverpeasUD);
                }
            }
        }
    }

    private void performSaveOfUsersDuringSynchronization(final SyncOfUsersContext context,
            final UserDetail[] distantUDs, final UserDetail[] silverpeasUDs) {
        SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_USERS, "Saving users in database...");
        final Map<String, UserDetail> indexedSpUsers = indexUsersBySpecificIdAndLogin(silverpeasUDs);
        for (final UserDetail distantUD : distantUDs) {
            final UserDetail userToUpdateFromDistantUser = getUserBySpecificIdOrFallbackLoginFrom(distantUD,
                    indexedSpUsers);
            if (userToUpdateFromDistantUser != null) {
                if (userToUpdateFromDistantUser.isRemovedState()) {
                    restoreUserDuringSynchronization(context, distantUD, userToUpdateFromDistantUser);
                } else if (mergeDistantUserIntoSilverpeasUser(distantUD, userToUpdateFromDistantUser)) {
                    updateUserDuringSynchronization(context, userToUpdateFromDistantUser);
                }
            } else if (context.isAddOperationToPerform()) {
                deleteRemovedUserDuringSynchronization(context, distantUD, indexedSpUsers);
                distantUD.setDomainId(context.getDomainId());
                addUserDuringSynchronization(context, distantUD);
            }
        }
    }

    /**
     * Background process request which ensure the reminder scheduler to not be disturbed by user
     * notification send processing.
     */
    private static class BackgroundUserIndexationProcess extends AbstractBackgroundProcessRequest {

        private final DomainDriverManager domainDriverManager;
        private final SyncOfUsersContext context;

        private BackgroundUserIndexationProcess(final DomainDriverManager domainDriverManager,
                final SyncOfUsersContext context) {
            super();
            this.domainDriverManager = domainDriverManager;
            this.context = context;
        }

        @Override
        protected void process() {
            final SilverLogger logger = SilverLogger.getLogger(this);
            final long start = System.currentTimeMillis();
            final String totalOfUsers = String
                    .valueOf(context.getAddedUsers().size() + context.getUpdatedUsers().size()
                            + context.getRemovedUsers().size() + context.getRestoredUsers().size());
            logger.debug(format("Starting indexation of {0} users on domain id {1}...", totalOfUsers,
                    context.getDomainId()));
            Transaction.performInOne(() -> {
                logger.debug(format("unindexation of {0} removed users on domain id {1}...",
                        String.valueOf(context.getRemovedUsers().size()), context.getDomainId()));
                context.getRemovedUsers().keySet().forEach(domainDriverManager::unindexUser);
                logger.debug(format("indexation of {0} restored users on domain id {1}...",
                        String.valueOf(context.getRestoredUsers().size()), context.getDomainId()));
                context.getRestoredUsers().keySet().forEach(domainDriverManager::indexUser);
                logger.debug(format("indexation of {0} added users on domain id {1}...",
                        String.valueOf(context.getAddedUsers().size()), context.getDomainId()));
                context.getAddedUsers().keySet().forEach(domainDriverManager::indexUser);
                logger.debug(format("indexation of {0} updated users on domain id {1}...",
                        String.valueOf(context.getUpdatedUsers().size()), context.getDomainId()));
                context.getUpdatedUsers().keySet().forEach(domainDriverManager::indexUser);
                return null;
            });
            final long end = System.currentTimeMillis();
            logger.debug(() -> format("Ending indexation of {0} users on domain id {1} in {2}", totalOfUsers,
                    context.getDomainId(), DurationFormatUtils.formatDurationHMS(end - start)));
        }
    }

    @Nullable
    private UserDetail getUserBySpecificIdOrFallbackLoginFrom(@Nonnull final UserDetail user,
            final Map<String, UserDetail> indexedUsers) {
        UserDetail indexedUser = indexedUsers.get(user.getSpecificId());
        if (indexedUser == null && shouldFallbackUserLogins) {
            indexedUser = indexedUsers.get(user.getLogin());
        }
        return indexedUser;
    }

    private boolean existsUserBySpecificIdOrFallbackLoginIn(final UserDetail user, final Set<String> indexedUsers) {
        return indexedUsers.contains(user.getSpecificId())
                || (shouldFallbackUserLogins && indexedUsers.contains(user.getLogin()));
    }

    @Nonnull
    private Map<String, UserDetail> indexUsersBySpecificIdAndLogin(final UserDetail[] silverpeasUDs) {
        final Map<String, UserDetail> indexedSilverpeasUsers = new HashMap<>(silverpeasUDs.length * 2);
        Arrays.stream(silverpeasUDs).forEach(u -> {
            indexedSilverpeasUsers.put(u.getSpecificId(), u);
            indexedSilverpeasUsers.put(u.getLogin(), u);
        });
        return indexedSilverpeasUsers;
    }

    @Nonnull
    private Set<String> extractUserSpecificIdAndFallbackLogin(final UserDetail[] users) {
        final Set<String> indexedUsers = new HashSet<>(
                shouldFallbackUserLogins ? (users.length * 2) : users.length);
        Arrays.stream(users).forEach(u -> {
            indexedUsers.add(u.getSpecificId());
            if (shouldFallbackUserLogins) {
                indexedUsers.add(u.getLogin());
            }
        });
        return indexedUsers;
    }

    /**
     * @param silverpeasUDs existing users after synchronization
     * @return a Map <specificId, userId>
     */
    private HashMap<String, String> getUserIdsMapping(UserDetail[] silverpeasUDs) {
        HashMap<String, String> ids = new HashMap<>();
        for (UserDetail user : silverpeasUDs) {
            ids.put(user.getSpecificId(), user.getId());
        }
        return ids;
    }

    private void updateUserDuringSynchronization(final SyncOfUsersContext context, final UserDetail distantUD) {
        final String specificId = distantUD.getSpecificId();
        try {
            final String silverpeasId = userManager.updateUser(distantUD, false);
            context.getUpdatedUsers().put(silverpeasId, distantUD);
            final String message = format("{0} {1} updated (id:{2} / specificId:{3})", USER,
                    distantUD.getDisplayedName(), silverpeasId, specificId);
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
            context.appendToReport(message).appendToReport("\n");
        } catch (AdminException aeMaj) {
            SilverLogger.getLogger(this).error("Full synchro: error while updating user " + specificId, aeMaj);
            final String errorMessage = format("problem updating user {0} (specificId:{1}) - {2}",
                    distantUD.getDisplayedName(), specificId, aeMaj.getMessage());
            context.appendToReport(errorMessage).appendToReport("\n");
            SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, errorMessage);
            context.appendToReport("user hasn't been updated\n");
        }
    }

    private void addUserDuringSynchronization(final SyncOfUsersContext context, final UserDetail distantUD) {
        final String specificId = distantUD.getSpecificId();
        try {
            final String silverpeasId = userManager.addUser(distantUD, true, false);
            if (silverpeasId.equals("")) {
                final String message = format(
                        "problem adding user {0} (specificId:{1}) - Login and LastName must be set !!!",
                        distantUD.getDisplayedName(), specificId);
                context.appendToReport(message).appendToReport("\n");
                SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, message);
                context.appendToReport("user has not been added\n");
            } else {
                context.getAddedUsers().put(silverpeasId, distantUD);
                final String message = format("{0} {1} added (id:{2} / specificId:{3})", USER,
                        distantUD.getDisplayedName(), silverpeasId, specificId);
                context.appendToReport(message).appendToReport("\n");
                SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
            }
        } catch (AdminException ae) {
            SilverLogger.getLogger(this).error("Full synchro: error while adding user " + specificId, ae);
            final String message = format("problem adding user {0}(specificId:{1}) - {2}",
                    distantUD.getDisplayedName(), specificId, ae.getMessage());
            SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, message);
            context.appendToReport(message).appendToReport("\n");
            context.appendToReport("user has not been added\n");
        }
    }

    private void removeUserDuringSynchronization(final SyncOfUsersContext context, final UserDetail silverpeasUD) {
        final String specificId = silverpeasUD.getSpecificId();
        try {
            userManager.removeUser(silverpeasUD, false);
            silverpeasUD.setState(UserState.REMOVED);
            context.getRemovedUsers().put(silverpeasUD.getId(), silverpeasUD);
            final String message = format("{0} {1} removed (id:{2} / specificId:{3})", USER,
                    silverpeasUD.getDisplayedName(), silverpeasUD.getId(), specificId);
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
        } catch (AdminException aeDel) {
            SilverLogger.getLogger(this).error("Full synchro: error while removing user " + specificId, aeDel);
            final String message = format("problem removing user {0} (specificId:{1}) - {2}",
                    silverpeasUD.getDisplayedName(), specificId, aeDel.getMessage());
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, message);
            context.appendToReport("user has not been removed\n");
        }
    }

    private void restoreUserDuringSynchronization(final SyncOfUsersContext context, final UserDetail distantUD,
            final UserDetail silverpeasUD) {
        final String specificId = silverpeasUD.getSpecificId();
        try {
            userManager.restoreUser(silverpeasUD, false);
            silverpeasUD.setState(UserState.VALID);
            if (mergeDistantUserIntoSilverpeasUser(distantUD, silverpeasUD)) {
                userManager.updateUser(silverpeasUD, false);
            }
            context.getRestoredUsers().put(silverpeasUD.getId(), silverpeasUD);
            final String message = format("{0} {1} restored (id:{2} / specificId:{3})", USER,
                    silverpeasUD.getDisplayedName(), silverpeasUD.getId(), specificId);
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_USERS, message);
        } catch (AdminException aeDel) {
            SilverLogger.getLogger(this).error("Full synchro: error while restoring user " + specificId, aeDel);
            final String message = format("problem restoring user {0} (specificId:{1}) - {2}",
                    silverpeasUD.getDisplayedName(), specificId, aeDel.getMessage());
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, message);
            context.appendToReport("user has not been restored\n");
        }
    }

    private void deleteRemovedUserDuringSynchronization(final SyncOfUsersContext context,
            final UserDetail distantUD, final Map<String, UserDetail> indexedSpUsers) {
        final String login = distantUD.getLogin();
        try {
            if (!shouldFallbackUserLogins) {
                final UserDetail silverpeasUser = indexedSpUsers.get(login);
                if (silverpeasUser != null) {
                    if (!distantUD.getSpecificId().equals(silverpeasUser.getSpecificId())
                            && silverpeasUser.isRemovedState()) {
                        userManager.deleteUser(silverpeasUser, true);
                        context.getDeletedUsers().put(silverpeasUser.getId(), silverpeasUser);
                        final String message = format("{0} {1} deleted (id:{2} / login:{3})", USER,
                                distantUD.getDisplayedName(), distantUD.getId(), login);
                        context.appendToReport(message).appendToReport("\n");
                        SynchroDomainReport.info(ADMIN_SYNCHRONIZE_USERS, message);
                    } else {
                        final String message = format(
                                "{0} {1} must have 'REMOVED' state for deletion (id:{2} / login:{3})", USER,
                                distantUD.getDisplayedName(), distantUD.getId(), login);
                        throw new AdminException(message);
                    }
                }
            }
        } catch (AdminException aeDel) {
            SilverLogger.getLogger(this).error("Full synchro: error while deleting user with login " + login,
                    aeDel);
            final String message = format("problem deleting user {0} (domainId:{1}, login:{2}) - {3}",
                    distantUD.getDisplayedName(), context.getDomainId(), login, aeDel.getMessage());
            context.appendToReport(message).appendToReport("\n");
            SynchroDomainReport.warn(ADMIN_SYNCHRONIZE_USERS, message);
            context.appendToReport("user has not been deleted\n");
        }
    }

    private void processSpecificSynchronization(String domainId, Collection<UserDetail> usersAdded,
            Collection<UserDetail> usersUpdated, Collection<UserDetail> usersRemoved) throws AdminException {
        Domain theDomain = domainDriverManager.getDomain(domainId);
        SettingBundle propDomainLdap = theDomain.getSettings();
        String nomClasseSynchro = propDomainLdap.getString("synchro.Class", null);
        if (StringUtil.isDefined(nomClasseSynchro)) {
            Collection<UserDetail> added = usersAdded;
            Collection<UserDetail> updated = usersUpdated;
            Collection<UserDetail> removed = usersRemoved;
            if (added == null) {
                added = new ArrayList<>();
            }
            if (updated == null) {
                updated = new ArrayList<>();
            }
            if (removed == null) {
                removed = new ArrayList<>();
            }
            try {
                LDAPSynchroUserItf synchroUser = (LDAPSynchroUserItf) Class.forName(nomClasseSynchro).newInstance();
                if (synchroUser != null) {
                    synchroUser.processUsers(added, updated, removed);
                }
            } catch (Exception e) {
                SilverLogger.getLogger(this).error(e);
            }
        }
    }

    /**
     * Synchronize groups between cache and domain's datastore
     */
    private String synchronizeGroups(String domainId, Map<String, String> userIds) throws AdminException {
        boolean bFound;
        String specificId;
        StringBuilder sReport = new StringBuilder("GroupDetail synchronization : \n");
        Map<String, GroupDetail> allDistantGroups = new HashMap<>();
        int iNbGroupsAdded = 0;
        int iNbGroupsMaj = 0;
        int iNbGroupsDeleted = 0;
        SynchroDomainReport.info(ADMIN_SYNCHRONIZE_GROUPS, "Starting groups synchronization...");
        try {
            // Get all root groups of the domain from distant datasource
            GroupDetail[] distantRootGroups = domainDriverManager.getAllRootGroups(domainId);
            // Get all groups of the domain from Silverpeas
            GroupDetail[] silverpeasGroups = groupManager.getGroupsOfDomain(domainId);

            SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_GROUPS, "Adding or updating groups in database...");
            // Check for new groups resursively
            final CheckoutGroupDescriptor descriptor = new CheckoutGroupDescriptor().setDomainId(domainId)
                    .setExistingGroups(silverpeasGroups).setTestedGroups(distantRootGroups)
                    .setAllIncludedGroups(allDistantGroups).setUserIds(userIds).setNbGroupsAdded(iNbGroupsAdded)
                    .setNbGroupsUpdated(iNbGroupsMaj);
            sReport.append(checkOutGroups(descriptor));

            // Delete obsolete groups
            SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_GROUPS, "Removing groups from database...");
            GroupDetail[] distantGroups = allDistantGroups.values().toArray(new GroupDetail[0]);
            for (GroupDetail silverpeasGroup : silverpeasGroups) {
                bFound = false;
                specificId = silverpeasGroup.getSpecificId();

                // search for group in distant datasource
                for (int nJ = 0; nJ < distantGroups.length && !bFound; nJ++) {
                    if (distantGroups[nJ].getSpecificId().equals(specificId)
                            || (shouldFallbackGroupNames && distantGroups[nJ].getName().equals(specificId))) {
                        bFound = true;
                    }
                }

                // if found, do nothing, else delete
                if (!bFound) {
                    iNbGroupsDeleted = synchroDeleteGroup(specificId, silverpeasGroup, sReport, iNbGroupsDeleted);
                }
            }
            sReport.append("Groups synchronization terminated\n");
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_GROUPS, "# of groups updated : " + iNbGroupsMaj
                    + ", added : " + iNbGroupsAdded + ", deleted : " + iNbGroupsDeleted);
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_GROUPS, "Groups synchronization terminated");
            return sReport.toString();
        } catch (Exception e) {
            SynchroDomainReport.error(ADMIN_SYNCHRONIZE_GROUPS,
                    "Problme lors de la synchronisation des groupes : " + e.getMessage(), null);
            throw new AdminException("Fails to synchronize groups in domain " + domainId + ".Report: " + sReport,
                    e);
        }
    }

    private int synchroDeleteGroup(final String specificId, final GroupDetail silverpeasGroup,
            final StringBuilder sReport, int iNbGroupsDeleted) {
        try {
            groupManager.deleteGroup(silverpeasGroup, true);
            iNbGroupsDeleted++;
            sReport.append("deleting group " + silverpeasGroup.getName() + "(id:" + specificId + ")\n");
            SynchroDomainReport.info(ADMIN_SYNCHRONIZE_GROUPS,
                    "GroupDetail " + silverpeasGroup.getName() + " deleted (SpecificId:" + specificId + ")");
        } catch (AdminException aeDel) {
            SilverLogger.getLogger(this).error("Full synchro: error while deleting group " + specificId, aeDel);
            sReport.append("problem deleting group " + silverpeasGroup.getName() + " (specificId:" + specificId
                    + ") - " + aeDel.getMessage() + "\n");
            sReport.append("group has not been deleted\n");
        }
        return iNbGroupsDeleted;
    }

    /**
     * Checks for new groups resursively
     */
    // Au 1er appel : (domainId,silverpeasGroups,distantRootGroups,
    // allDistantGroups(vide), userIds, null)
    // No need to refresh cache : the cache is reseted at the end of the
    // synchronization
    private String checkOutGroups(final CheckoutGroupDescriptor descriptor) throws AdminException {
        StringBuilder report = new StringBuilder();
        // Add new groups or update existing ones from distant data source
        descriptor.addTestedGroupsInAllIncludedGroups();
        for (GroupDetail testedGroup : descriptor.getTestedGroups()) {
            // Prepare GroupDetail to be at Silverpeas format
            testedGroup.setDomainId(descriptor.getDomainId());
            final String specificId = testedGroup.getSpecificId();

            // search for group in Silverpeas database
            Optional<GroupDetail> foundGroup = Arrays.stream(descriptor.getExistingGroups())
                    .filter(g -> g.getSpecificId().equals(specificId)
                            || (shouldFallbackGroupNames && g.getSpecificId().equals(testedGroup.getName())))
                    .findFirst();
            if (foundGroup.isPresent()) {
                testedGroup.setId(foundGroup.get().getId());
                SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                        "avant maj du groupe " + specificId + ", recherche de ses groupes parents");
            } else {
                SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                        "avant ajout du groupe " + specificId + ", recherche de ses groupes parents");
            }

            setParentGroup(descriptor, specificId, testedGroup);
            setUserIds(descriptor, testedGroup);
            // if found, update, else create
            final String silverpeasId;
            if (foundGroup.isPresent()) {
                silverpeasId = updateGroup(descriptor, specificId, testedGroup, report);
            } else { // AJOUT
                silverpeasId = addGroup(descriptor, specificId, testedGroup, report);
            }
            // Recurse with subgroups
            recursWithSubGroups(descriptor, specificId, silverpeasId, report);
        }
        return report.toString();
    }

    private void recursWithSubGroups(final CheckoutGroupDescriptor descriptor, final String specificId,
            final String silverpeasId, final StringBuilder report) throws AdminException {
        if (silverpeasId != null && silverpeasId.length() > 0) {
            GroupDetail[] subGroups = domainDriverManager.getGroups(silverpeasId);
            if (subGroups != null && subGroups.length > 0) {
                GroupDetail[] cleanSubGroups = removeCrossReferences(subGroups, descriptor.getAllIncludedGroups(),
                        specificId);
                if (cleanSubGroups != null && cleanSubGroups.length > 0) {
                    SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS, "Ajout ou mise  jour de "
                            + cleanSubGroups.length + " groupes fils du groupe " + specificId + "...");
                    descriptor.setTestedGroups(cleanSubGroups).setSuperGroupId(silverpeasId);
                    report.append(checkOutGroups(descriptor));
                }
            }
        }
    }

    @Nullable
    private String addGroup(final CheckoutGroupDescriptor descriptor, final String specificId,
            final GroupDetail testedGroup, final StringBuilder report) {
        String silverpeasId = null;
        try {
            silverpeasId = groupManager.addGroup(testedGroup, true);
            if (StringUtil.isDefined(silverpeasId)) {
                descriptor.setNbGroupsAdded(descriptor.getNbGroupsAdded() + 1);

                report.append("adding group " + testedGroup.getName() + "(id:" + specificId + ")\n");
                SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                        "ajout groupe " + testedGroup.getName() + ID_IS + silverpeasId + ") OK");
            } else { // le name groupe non renseign

                report.append("problem adding group id : " + specificId + "\n");
            }
        } catch (AdminException aeAdd) {

            report.append("problem adding group " + testedGroup.getName() + ID_IS + specificId + ") "
                    + aeAdd.getMessage() + "\n");
            report.append("group has not been added\n");
        }
        return silverpeasId;
    }

    private String updateGroup(final CheckoutGroupDescriptor descriptor, final String specificId,
            final GroupDetail testedGroup, final StringBuilder report) {
        final String result;
        String silverpeasId = null;
        try {
            result = groupManager.updateGroup(testedGroup, true);
            if (StringUtil.isDefined(result)) {
                descriptor.setNbGroupsUpdated(descriptor.getNbGroupsUpdated() + 1);
                silverpeasId = testedGroup.getId();
                report.append("updating group " + testedGroup.getName() + "(id:" + specificId + ")\n");
                SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                        "maj groupe " + testedGroup.getName() + ID_IS + silverpeasId + ") OK");
            } else {
                // le name groupe non renseign
                SilverLogger.getLogger(this).error("Full Synchro: error while updating group {0}", specificId);
                report.append("problem updating group id : " + specificId + "\n");
            }
        } catch (AdminException aeMaj) {
            SilverLogger.getLogger(this).error("Full Synchro: error while updating group {0}: ", specificId,
                    aeMaj.getMessage());
            report.append("problem updating group " + testedGroup.getName() + ID_IS + specificId + ") "
                    + aeMaj.getMessage() + "\n");
            report.append("group has not been updated\n");
        }
        return silverpeasId;
    }

    private void setUserIds(final CheckoutGroupDescriptor descriptor, final GroupDetail testedGroup) {
        String[] userSpecificIds = testedGroup.getUserIds();
        List<String> convertedUserIds = new ArrayList<>();
        for (String userSpecificId : userSpecificIds) {
            if (descriptor.getUserIds().get(userSpecificId) != null) {
                convertedUserIds.add(descriptor.getUserIds().get(userSpecificId));
            }
        }
        // Le groupe contiendra une liste d'IDs de users existant ds la base et
        // non + une liste de logins rcuprs via LDAP
        testedGroup.setUserIds(convertedUserIds.toArray(new String[0]));
    }

    private void setParentGroup(final CheckoutGroupDescriptor descriptor, final String specificId,
            final GroupDetail testedGroup) throws AdminException {
        String[] groupParentsIds = domainDriverManager.getGroupMemberGroupIds(descriptor.getDomainId(),
                testedGroup.getSpecificId());
        if ((groupParentsIds == null) || (groupParentsIds.length == 0)) {
            testedGroup.setSuperGroupId(null);
            SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                    "le groupe " + specificId + " n'a pas de pre");
        } else {
            testedGroup.setSuperGroupId(descriptor.getSuperGroupId());
            if (descriptor.getSuperGroupId() != null)// scurit
            {
                SynchroDomainReport.debug(ADMIN_SYNCHRONIZE_CHECK_OUT_GROUPS,
                        "le groupe " + specificId + " a pour pre le groupe "
                                + domainDriverManager.getGroup(descriptor.getSuperGroupId()).getSpecificId()
                                + " d'Id base " + descriptor.getSuperGroupId());
            }
        }
    }

    /**
     * Remove cross reference risk between groups
     */
    private GroupDetail[] removeCrossReferences(GroupDetail[] subGroups,
            Map<String, GroupDetail> allIncluededGroups, String fatherId) {
        ArrayList<GroupDetail> cleanSubGroups = new ArrayList<>();
        //noinspection UnusedAssignment,UnusedAssignment,UnusedAssignment
        for (GroupDetail subGroup : subGroups) {
            if (allIncluededGroups.get(subGroup.getSpecificId()) == null) {
                cleanSubGroups.add(subGroup);
            } else {
                SilverLogger.getLogger(this).warn("Cross deletion for child {0} of the father {1}",
                        subGroup.getSpecificId(), fatherId);
            }
        }
        return cleanSubGroups.toArray(new GroupDetail[cleanSubGroups.size()]);
    }

    @Override
    public List<String> searchUserIdsByProfile(final List<String> profileIds) throws AdminException {
        Set<String> userIds = new HashSet<>();
        // search users in profiles
        try {
            for (String profileId : profileIds) {
                ProfileInst profile = profileManager.getProfileInst(profileId);
                // add users directly attach to profile
                addAllUsersInProfile(profile, userIds);
            }
        } catch (Exception e) {
            throw new AdminException("Fail to search user ids by some profiles", e);
        }

        return new ArrayList<>(userIds);
    }

    // -------------------------------------------------------------------------
    // For SelectionPeas
    // -------------------------------------------------------------------------

    @Override
    public ListSlice<UserDetail> searchUsers(final UserDetailsSearchCriteria searchCriteria) throws AdminException {
        List<String> userIds = null;
        if (searchCriteria.isCriterionOnComponentInstanceIdSet()) {
            userIds = searchUsersInComponentInstance(searchCriteria, userIds);
        }

        if (searchCriteria.isCriterionOnUserIdsSet()) {
            userIds = searchUserByTheirIds(searchCriteria, userIds);
        }

        SearchCriteriaDAOFactory factory = SearchCriteriaDAOFactory.getFactory();
        UserSearchCriteriaForDAO criteria = factory.getUserSearchCriteriaDAO();
        if (userIds != null) {
            criteria.onUserIds(userIds.toArray(new String[0]));
        }
        if (searchCriteria.isCriterionOnGroupIdsSet()) {
            setCriteriaWithGroupIds(searchCriteria, criteria);
        }
        if (searchCriteria.isCriterionOnDomainIdSet()) {
            criteria.and().onDomainIds(searchCriteria.getCriterionOnDomainIds());
        }
        if (searchCriteria.isCriterionOnUserSpecificIdsSet()) {
            criteria.and().onUserSpecificIds(searchCriteria.getCriterionOnUserSpecificIds());
        }
        if (searchCriteria.isCriterionOnAccessLevelsSet()) {
            criteria.and().onAccessLevels(searchCriteria.getCriterionOnAccessLevels());
        }
        if (searchCriteria.isCriterionOnUserStatesToExcludeSet()) {
            criteria.and().onUserStatesToExclude(searchCriteria.getCriterionOnUserStatesToExclude());
        }
        if (searchCriteria.isCriterionOnNameSet()) {
            criteria.and().onName(searchCriteria.getCriterionOnName());
        } else {
            if (searchCriteria.isCriterionOnFirstNameSet()) {
                criteria.and().onFirstName(searchCriteria.getCriterionOnFirstName());
            }
            if (searchCriteria.isCriterionOnLastNameSet()) {
                criteria.and().onLastName(searchCriteria.getCriterionOnLastName());
            }
        }
        if (searchCriteria.isCriterionOnPaginationSet()) {
            criteria.onPagination(searchCriteria.getCriterionOnPagination());
        }

        return userManager.getUsersMatchingCriteria(criteria);
    }

    private void setCriteriaWithGroupIds(final UserDetailsSearchCriteria searchCriteria,
            final UserSearchCriteriaForDAO criteria) throws AdminException {
        String[] theGroupIds = searchCriteria.getCriterionOnGroupIds();
        if (theGroupIds == UserDetailsSearchCriteria.ANY_GROUPS) {
            criteria.and().onGroupIds(SearchCriteria.ANY);
        } else {
            Set<String> groupIds = new HashSet<>();
            for (String aGroupId : theGroupIds) {
                groupIds.addAll(groupManager.getAllSubGroupIdsRecursively(aGroupId));
                groupIds.add(aGroupId);
            }
            criteria.and().onGroupIds(groupIds.toArray(new String[groupIds.size()]));
        }
    }

    @NotNull
    private List<String> searchUserByTheirIds(final UserDetailsSearchCriteria searchCriteria,
            List<String> userIds) {
        if (userIds == null) {
            userIds = Arrays.asList(searchCriteria.getCriterionOnUserIds());
        } else {
            List<String> userIdsInCriterion = Arrays.asList(searchCriteria.getCriterionOnUserIds());
            List<String> userIdsToTake = new ArrayList<>();
            for (String userId : userIds) {
                if (userIdsInCriterion.contains(userId)) {
                    userIdsToTake.add(userId);
                }
            }
            userIds = userIdsToTake;
        }
        return userIds;
    }

    @Nullable
    private List<String> searchUsersInComponentInstance(final UserDetailsSearchCriteria searchCriteria,
            final List<String> userIds) throws AdminException {
        List<String> listOfRoleNames = Collections.emptyList();
        List<String> result = userIds == null ? new ArrayList<>() : userIds;
        if (searchCriteria.isCriterionOnRoleNamesSet()) {
            listOfRoleNames = Arrays.asList(searchCriteria.getCriterionOnRoleNames());
        }
        SilverpeasComponentInstance instance = getComponentInstance(
                searchCriteria.getCriterionOnComponentInstanceId());
        if (!listOfRoleNames.isEmpty() || !instance.isPublic()) {
            result = new ArrayList<>();
            if (!instance.isPersonal()) {
                addUserIdsByCriteria(instance, searchCriteria, listOfRoleNames, result);
            } else {
                final User user = ((SilverpeasPersonalComponentInstance) instance).getUser();
                final Collection<String> userRoles = instance.getSilverpeasRolesFor(user).stream().map(Enum::name)
                        .collect(Collectors.toList());
                if (!CollectionUtil.intersection(userRoles, listOfRoleNames).isEmpty()) {
                    result.add(user.getId());
                }
            }
            if (result.isEmpty()) {
                result = null;
            }
        }
        return result;
    }

    private void addUserIdsByCriteria(final SilverpeasComponentInstance instance,
            final UserDetailsSearchCriteria searchCriteria, final List<String> listOfRoleNames,
            final List<String> result) throws AdminException {
        List<ProfileInst> profiles;
        if (searchCriteria.isCriterionOnResourceIdSet()) {
            profiles = getProfileInstsFor(searchCriteria.getCriterionOnResourceId(), instance.getId());
        } else {
            profiles = getComponentInst(instance.getId()).getAllProfilesInst();
        }
        for (ProfileInst aProfile : profiles) {
            if (listOfRoleNames.isEmpty() || listOfRoleNames.contains(aProfile.getName())) {
                addAllUsersInProfile(aProfile, result);
            }
        }
    }

    private void addAllUsersInProfile(final ProfileInst aProfile, final Collection<String> userIds)
            throws AdminException {
        userIds.addAll(aProfile.getAllUsers());

        // users of the groups (and recursively of their subgroups) playing the role
        List<String> groupIds = aProfile.getAllGroups();
        List<String> allGroupIds = new ArrayList<>();
        for (String aGroupId : groupIds) {
            allGroupIds.add(aGroupId);
            allGroupIds.addAll(groupManager.getAllSubGroupIdsRecursively(aGroupId));
        }
        userIds.addAll(userManager.getAllUserIdsInGroups(allGroupIds));
    }

    @Override
    public SilverpeasList<GroupDetail> searchGroups(final GroupsSearchCriteria searchCriteria)
            throws AdminException {
        SearchCriteriaDAOFactory factory = SearchCriteriaDAOFactory.getFactory();
        GroupSearchCriteriaForDAO criteria = factory.getGroupSearchCriteriaDAO();
        if (searchCriteria.isCriterionOnComponentInstanceIdSet()) {
            makeCriteriaOnComponentInstanceId(searchCriteria, criteria);
        }

        if (searchCriteria.childrenRequired()) {
            criteria.withChildren();
        }

        if (searchCriteria.mustBeRoot()) {
            criteria.onAsRootGroup();
        }

        if (searchCriteria.isCriterionOnDomainIdSet()) {
            String domainId = searchCriteria.getCriterionOnDomainId();
            if (searchCriteria.isCriterionOnMixedDomainIdSet()) {
                criteria.onMixedDomainOrOnDomainId(domainId);
            } else {
                criteria.onDomainIds(domainId);
            }
        }

        if (searchCriteria.isCriterionOnGroupIdsSet()) {
            criteria.and().onGroupIds(searchCriteria.getCriterionOnGroupIds());
        }

        if (searchCriteria.isCriterionOnNameSet()) {
            criteria.and().onName(searchCriteria.getCriterionOnName());
        }

        if (searchCriteria.isCriterionOnAccessLevelsSet()) {
            criteria.and().onAccessLevels(searchCriteria.getCriterionOnAccessLevels());
        }

        if (searchCriteria.isCriterionOnUserStatesToExcludeSet()) {
            criteria.and().onUserStatesToExclude(searchCriteria.getCriterionOnUserStatesToExclude());
        }

        if (searchCriteria.isCriterionOnSuperGroupIdSet()) {
            criteria.and().onSuperGroupId(searchCriteria.getCriterionOnSuperGroupId());
        }

        if (searchCriteria.isCriterionOnPaginationSet()) {
            criteria.onPagination(searchCriteria.getCriterionOnPagination());
        }

        return groupManager.getGroupsMatchingCriteria(criteria);
    }

    private void makeCriteriaOnComponentInstanceId(final GroupsSearchCriteria searchCriteria,
            final GroupSearchCriteriaForDAO criteria) throws AdminException {
        final List<String> listOfRoleNames = new ArrayList<>();
        if (searchCriteria.isCriterionOnRoleNamesSet()) {
            listOfRoleNames.addAll(Arrays.asList(searchCriteria.getCriterionOnRoleNames()));
        }
        SilverpeasComponentInstance instance = getComponentInstance(
                searchCriteria.getCriterionOnComponentInstanceId());
        if (!listOfRoleNames.isEmpty() || !instance.isPublic()) {
            List<String> roleIds = new ArrayList<>();
            if (!instance.isPersonal()) {
                List<ProfileInst> profiles;
                if (searchCriteria.isCriterionOnResourceIdSet()) {
                    profiles = getProfileInstsFor(searchCriteria.getCriterionOnResourceId(), instance.getId());
                } else {
                    profiles = getComponentInst(instance.getId()).getAllProfilesInst();
                }
                profiles.stream().filter(p -> listOfRoleNames.isEmpty() || listOfRoleNames.contains(p.getName()))
                        .forEach(p -> roleIds.add(p.getId()));
            }
            criteria.onRoleNames(roleIds.toArray(new String[roleIds.size()]));
        }
    }

    // -------------------------------------------------------------------------
    // Node profile management
    // -------------------------------------------------------------------------
    @Override
    public void indexAllUsers() throws AdminException {
        Domain[] domains = getAllDomains();
        for (Domain domain : domains) {
            try {
                indexUsers(domain.getId());
            } catch (Exception e) {
                SilverLogger.getLogger(this).error(e);
            }
        }
    }

    @Override
    public void indexUsers(String domainId) throws AdminException {
        try {
            domainDriverManager.indexAllUsers(domainId);
        } catch (Exception e) {
            throw new AdminException(failureOnIndexing("users in domain", domainId), e);
        }
    }

    @Override
    public String copyAndPasteComponent(PasteDetail pasteDetail) throws AdminException, QuotaException {
        if (!StringUtil.isDefined(pasteDetail.getToSpaceId())) {
            // cannot paste component on root
            return null;
        }
        ComponentInst newCompo = (ComponentInst) getComponentInst(pasteDetail.getFromComponentId()).clone();
        SpaceInst destinationSpace = getSpaceInstById(pasteDetail.getToSpaceId());

        String lang = newCompo.getLanguage();
        if (StringUtil.isNotDefined(lang)) {
            lang = I18NHelper.defaultLanguage;
        }
        // Creation
        newCompo.setLocalId(-1);
        newCompo.setDomainFatherId(destinationSpace.getId());
        newCompo.setOrderNum(destinationSpace.getNumComponentInst());
        newCompo.setCreateDate(new Date());
        newCompo.setCreatorUserId(pasteDetail.getUserId());
        newCompo.setLanguage(lang);

        // Rename if componentName already exists in the destination space
        String label = renameComponentName(newCompo.getLabel(lang), destinationSpace.getAllComponentsInst());
        newCompo.setLabel(label);
        ComponentI18N translation = newCompo.getTranslation(lang);
        if (translation != null) {
            translation.setName(label);
        }

        // Delete inherited profiles only
        // It will be processed by admin
        newCompo.removeInheritedProfiles();

        // Add the component
        String sComponentId = addComponentInst(pasteDetail.getUserId(), newCompo);

        // Execute specific paste by the component
        try {
            pasteDetail.setToComponentId(sComponentId);
            ApplicationResourcePasting componentPaste = ServiceProvider.getServiceByComponentInstanceAndNameSuffix(
                    pasteDetail.getFromComponentId(), ApplicationResourcePasting.NAME_SUFFIX);
            componentPaste.paste(pasteDetail);
        } catch (IllegalStateException e) {
            SilverLogger.getLogger(this).silent(e);
        } catch (Exception e) {
            SilverLogger.getLogger(this).error(e);
        }
        return sComponentId;
    }

    /**
     * Rename component Label if necessary
     *
     * @param label
     * @param listComponents
     * @return
     */
    private String renameComponentName(String label, ArrayList<ComponentInst> listComponents) {
        String newComponentLabel = label;
        for (ComponentInst componentInst : listComponents) {
            if (componentInst.getLabel().equals(newComponentLabel)) {
                newComponentLabel = "Copie de " + label;
                return renameComponentName(newComponentLabel, listComponents);
            }
        }
        return newComponentLabel;
    }

    //check if spaceId is not parent of anotherSpace
    private boolean isParent(int spaceId, Integer anotherSpaceId) throws AdminException {
        if (anotherSpaceId == null || anotherSpaceId < 0) {
            return false;
        }
        List<SpaceInstLight> path = treeCache.getSpacePath(anotherSpaceId);
        if (path.isEmpty()) {
            path = getPathToSpace(String.valueOf(anotherSpaceId), true);
        }
        for (SpaceInstLight space : path) {
            if (spaceId == space.getLocalId()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String copyAndPasteSpace(PasteDetail pasteDetail) throws AdminException, QuotaException {
        String newSpaceId = null;
        String spaceId = pasteDetail.getFromSpaceId();
        String toSpaceId = pasteDetail.getToSpaceId();
        boolean pasteAllowed = !isParent(getDriverSpaceId(spaceId), getDriverSpaceId(toSpaceId));
        if (pasteAllowed) {
            // paste space itself
            SpaceInst oldSpace = getSpaceInstById(spaceId);
            SpaceInst newSpace = createPasteSpace(pasteDetail, oldSpace, toSpaceId);

            // Remove inherited profiles from cloned space
            newSpace.removeInheritedProfiles();

            // Remove components from cloned space
            List<ComponentInst> components = newSpace.getAllComponentsInst();
            newSpace.removeAllComponentsInst();

            // Add space
            newSpaceId = addSpaceInst(pasteDetail.getUserId(), newSpace);

            // Copy space quota
            copySpaceQuota(oldSpace, newSpace);

            // paste components
            String componentIdAsHomePage = pasteComponentsOfSpace(pasteDetail, newSpaceId, newSpace, components);

            // paste subspaces
            pasteSubspacesOfSpace(pasteDetail, newSpaceId, newSpace);

            // update parameter of space home page if needed
            String newFirstPageExtraParam = null;
            if (StringUtil.isDefined(componentIdAsHomePage)) {
                newFirstPageExtraParam = componentIdAsHomePage;
            } else if (newSpace.getFirstPageType() == SpaceInst.FP_TYPE_HTML_PAGE) {
                String oldURL = newSpace.getFirstPageExtraParam();
                newFirstPageExtraParam = oldURL.replaceAll(spaceId, newSpaceId);
            }

            if (StringUtil.isDefined(newFirstPageExtraParam)) {
                SpaceInst space = getSpaceInstById(newSpaceId);
                space.setFirstPageExtraParam(newFirstPageExtraParam);
                updateSpaceInst(space);
            }
        }
        return newSpaceId;
    }

    private void pasteSubspacesOfSpace(final PasteDetail pasteDetail, final String newSpaceId,
            final SpaceInst newSpace) throws AdminException, QuotaException {
        PasteDetail subSpacePasteDetail = new PasteDetail(pasteDetail.getUserId());
        subSpacePasteDetail.setOptions(pasteDetail.getOptions());
        subSpacePasteDetail.setToSpaceId(newSpaceId);
        List<SpaceInst> subSpaceInsts = newSpace.getSubSpaces();
        for (SpaceInst subSpaceInst : subSpaceInsts) {
            subSpacePasteDetail.setFromSpaceId(subSpaceInst.getId());
            copyAndPasteSpace(subSpacePasteDetail);
        }
    }

    @Nullable
    private String pasteComponentsOfSpace(final PasteDetail pasteDetail, final String newSpaceId,
            final SpaceInst newSpace, final List<ComponentInst> components) throws AdminException, QuotaException {
        // verify space homepage
        String componentIdAsHomePage = null;
        if (newSpace.getFirstPageType() == SpaceInst.FP_TYPE_COMPONENT_INST) {
            componentIdAsHomePage = newSpace.getFirstPageExtraParam();
        }

        // paste components of space
        PasteDetail componentPasteDetail = new PasteDetail(pasteDetail.getUserId());
        componentPasteDetail.setOptions(pasteDetail.getOptions());
        componentPasteDetail.setToSpaceId(newSpaceId);
        for (ComponentInst component : components) {
            componentPasteDetail.setFromComponentId(component.getId());
            String componentId = copyAndPasteComponent(componentPasteDetail);
            // check if new component must be used as home page of new space
            if (componentIdAsHomePage != null && componentIdAsHomePage.equals(component.getId())) {
                componentIdAsHomePage = componentId;
            }
        }
        return componentIdAsHomePage;
    }

    private void copySpaceQuota(final SpaceInst oldSpace, final SpaceInst newSpace) throws QuotaException {
        Quota dataStorageQuota = SpaceServiceProvider.getDataStorageSpaceQuotaService()
                .get(DataStorageSpaceQuotaKey.from(oldSpace));
        if (dataStorageQuota.exists()) {
            SpaceServiceProvider.getDataStorageSpaceQuotaService()
                    .initialize(DataStorageSpaceQuotaKey.from(newSpace), dataStorageQuota);
        }
        Quota componentQuota = SpaceServiceProvider.getComponentSpaceQuotaService()
                .get(ComponentSpaceQuotaKey.from(oldSpace));
        if (componentQuota.exists()) {
            SpaceServiceProvider.getComponentSpaceQuotaService().initialize(ComponentSpaceQuotaKey.from(newSpace),
                    componentQuota);
        }
    }

    @NotNull
    private SpaceInst createPasteSpace(final PasteDetail pasteDetail, final SpaceInst oldSpace,
            final String toSpaceId) throws AdminException {
        SpaceInst newSpace = oldSpace.clone();
        newSpace.setLocalId(-1);
        List<String> newBrotherIds;
        if (StringUtil.isDefined(toSpaceId)) {
            SpaceInst destinationSpace = getSpaceInstById(toSpaceId);
            newSpace.setDomainFatherId(destinationSpace.getId());
            List<SpaceInst> brothers = destinationSpace.getSubSpaces();
            newBrotherIds = new ArrayList<>(brothers.size());
            for (SpaceInst brother : brothers) {
                newBrotherIds.add(brother.getId());
            }
        } else {
            newSpace.setDomainFatherId("-1");
            newBrotherIds = Arrays.asList(getAllRootSpaceIds());
        }
        newSpace.setOrderNum(newBrotherIds.size());
        newSpace.setCreateDate(new Date());
        newSpace.setCreatorUserId(pasteDetail.getUserId());
        String lang = oldSpace.getLanguage();
        if (StringUtil.isNotDefined(lang)) {
            lang = I18NHelper.defaultLanguage;
        }
        newSpace.setLanguage(lang);

        // Rename if spaceName already used in the destination space
        List<SpaceInstLight> subSpaces = new ArrayList<>();
        for (String subSpaceId : newBrotherIds) {
            subSpaces.add(getSpaceInstLight(getDriverSpaceId(subSpaceId)));
        }
        String name = renameSpace(newSpace.getName(newSpace.getLanguage()), subSpaces);
        newSpace.setName(name);

        return newSpace;
    }

    private String renameSpace(String label, List<SpaceInstLight> listSpaces) {
        String newSpaceLabel = label;
        for (SpaceInstLight space : listSpaces) {
            if (space.getName().equals(newSpaceLabel)) {
                newSpaceLabel = "Copie de " + label;
                return renameSpace(newSpaceLabel, listSpaces);
            }
        }
        return newSpaceLabel;
    }

    /**
     * Gets all the profile instances defined for the specified resource in the specified component
     * instance.
     *
     * @param resourceId the unique identifier of a resource managed in a component instance.
     * @param instanceId the unique identifier of the component instance.
     * @return a list of profile instances.
     */
    private List<ProfileInst> getProfileInstsFor(String resourceId, String instanceId) throws AdminException {
        Pattern objectIdPattern = Pattern.compile("([a-zA-Z]+)(\\d+)");
        Matcher matcher = objectIdPattern.matcher(resourceId);
        if (matcher.matches() && matcher.groupCount() == 2) {
            String type = matcher.group(1);
            String id = matcher.group(2);
            return getProfilesByObject(id, type, instanceId);
        }
        throw new AdminPersistenceException(
                failureOnGetting("profiles on resource " + resourceId, "of component " + instanceId));
    }

    /**
     * Get all the space profiles Id for the given user without group transitivity.
     */
    private String[] getDirectSpaceProfileIdsOfUser(String sUserId) throws AdminException {
        try {
            return spaceProfileManager.getSpaceProfileIdsOfUserType(sUserId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("space profiles of user", sUserId), e);
        }
    }

    /**
     * Get all the space profiles Id for the given group
     */
    private String[] getDirectSpaceProfileIdsOfGroup(String groupId) throws AdminException {
        try {
            return spaceProfileManager.getSpaceProfileIdsOfGroupType(groupId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("space profiles of group", groupId), e);
        }
    }

    /**
     * Get all the component profiles Id for the given user without group transitivity.
     * (component object profiles are not retrieved)
     */
    @SuppressWarnings("unchecked")
    private String[] getDirectComponentProfileIdsOfUser(String sUserId) throws AdminException {
        try {
            return profileManager.getProfileIdsOfUser(sUserId, Collections.emptyList());
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("component profiles of user", sUserId), e);
        }
    }

    /**
     * Get all the component profiles Id for the given group.
     * (component object profiles are not retrieved)
     */
    @SuppressWarnings("unchecked")
    private String[] getDirectComponentProfileIdsOfGroup(String groupId) throws AdminException {
        try {
            return profileManager.getProfileIdsOfGroup(groupId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("component profiles of group", groupId), e);
        }
    }

    /**
     * Get all the component object profiles Id for the given user.
     * (direct component profiles are not retrieved)
     */
    @SuppressWarnings("unchecked")
    private String[] getComponentObjectProfileIdsOfUserType(String userId) throws AdminException {
        try {
            // retrieve value from database
            return profileManager.getAllComponentObjectProfileIdsOfUser(userId, Collections.emptyList());
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("component profiles of user", userId), e);
        }
    }

    /**
     * Get all the component object profiles Id for the given group.
     * (direct component profiles are not retrieved)
     */
    private String[] getComponentObjectProfileIdsOfGroupType(String sGroupId) throws AdminException {
        try {
            // retrieve value from database
            return profileManager.getAllComponentObjectProfileIdsOfGroup(sGroupId);
        } catch (Exception e) {
            throw new AdminException(failureOnGetting("component profiles of group", sGroupId), e);
        }
    }

    /*
     * Add target profiles (space, components, nodes)
     */
    private void addTargetProfiles(RightAssignationContext context, String[] sourceSpaceProfileIds,
            String[] sourceComponentProfileIds, String[] sourceNodeProfileIds) throws AdminException {

        //Add space rights (and sub-space and components, by inheritance)
        for (String spaceProfileId : sourceSpaceProfileIds) {
            SpaceProfileInst currentSourceSpaceProfile = getSpaceProfileInst(spaceProfileId);
            if (currentSourceSpaceProfile != null) {
                SpaceInstLight spaceInst = getSpaceInstLight(
                        getDriverSpaceId(currentSourceSpaceProfile.getSpaceFatherId()));
                if (spaceInst != null && !spaceInst.isPersonalSpace()) {
                    // do not treat the personal space
                    addInRightProfile(context, currentSourceSpaceProfile);
                    updateSpaceProfileInst(currentSourceSpaceProfile, context.getAuthor());
                }
            }
        }

        //Add component rights
        addComponentRights(context, sourceComponentProfileIds);

        //Add nodes rights
        addComponentRights(context, sourceNodeProfileIds);
    }

    private void addComponentRights(final RightAssignationContext context, final String[] sourceComponentProfileIds)
            throws AdminException {
        for (String profileId : sourceComponentProfileIds) {
            ProfileInst currentSourceProfile = getProfileInst(profileId);
            ComponentInst currentComponent = getComponentInst(currentSourceProfile.getComponentFatherId());
            String spaceId = currentComponent.getDomainFatherId();
            SpaceInstLight spaceInst = getSpaceInstLight(getDriverSpaceId(spaceId));
            if (currentComponent.getStatus() == null && spaceInst != null && !spaceInst.isPersonalSpace()) {
                // do not treat the personal space
                addInRightProfile(context, currentSourceProfile);
                doUpdateProfileInst(currentSourceProfile, context.getAuthor());
            }
        }
    }

    private void addInRightProfile(final RightAssignationContext context, final RightProfile rightProfile) {
        if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.USER) {
            rightProfile.addUser(context.getTargetId());
        } else if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.GROUP) {
            rightProfile.addGroup(context.getTargetId());
        }
    }

    /*
     * Delete target profiles about spaces, components and component objects (node for example).
     */
    private void deleteTargetProfiles(RightAssignationContext context,
            final String[] spaceProfileIdsToDeleteForTarget, final String[] componentProfileIdsForTarget)
            throws AdminException {

        // Delete space rights (and sub-space and components, by inheritance) for target
        for (String spaceProfileId : spaceProfileIdsToDeleteForTarget) {
            SpaceProfileInst currentTargetSpaceProfile = getSpaceProfileInst(spaceProfileId);
            SpaceInstLight spaceInst = getSpaceInstLight(
                    getDriverSpaceId(currentTargetSpaceProfile.getSpaceFatherId()));
            if (spaceInst != null && !spaceInst.isPersonalSpace()) {// do not treat the personal space
                removeFromRightProfile(context, currentTargetSpaceProfile);
                updateSpaceProfileInst(currentTargetSpaceProfile, context.getAuthor());
            }
        }

        // Delete component and node rights for target (it is the same Profile manager that handles
        // component and node rights)
        for (String profileId : componentProfileIdsForTarget) {
            ProfileInst currentTargetProfile = getProfileInst(profileId);
            ComponentInst currentComponent = getComponentInst(currentTargetProfile.getComponentFatherId());
            String spaceId = currentComponent.getDomainFatherId();
            SpaceInstLight spaceInst = getSpaceInstLight(getDriverSpaceId(spaceId));

            if (currentComponent.getStatus() == null && spaceInst != null && !spaceInst.isPersonalSpace()) {// do not treat the personal space
                removeFromRightProfile(context, currentTargetProfile);
                doUpdateProfileInst(currentTargetProfile, context.getAuthor());
            }
        }
    }

    private void removeFromRightProfile(final RightAssignationContext context, final RightProfile rightProfile) {
        if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.USER) {
            rightProfile.removeUser(context.getTargetId());
        } else if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.GROUP) {
            rightProfile.removeGroup(context.getTargetId());
        }
    }

    /**
     * Centralized method to copy or replace rights.
     * @param context the context that defined what the treatment must perform.
     * @throws AdminException
     */
    private void assignRightsFromSourceToTarget(RightAssignationContext context) throws AdminException {
        try {
            if (context.areSourceAndTargetEqual()) {
                //target = source, so nothing is done
                return;
            }

            String[] spaceProfileIdsToCopy = new String[0];
            String[] componentProfileIdsToCopy = new String[0];
            String[] componentObjectProfileIdsToCopy = new String[0];
            String[] spaceProfileIdsToReplace = new String[0];
            String[] componentProfileIdsToReplace = new String[0];

            // Loading existing source profile data identifiers
            if (context.getSourceType() == RightAssignationContext.RESOURCE_TYPE.USER) {
                spaceProfileIdsToCopy = getDirectSpaceProfileIdsOfUser(context.getSourceId());
                componentProfileIdsToCopy = getDirectComponentProfileIdsOfUser(context.getSourceId());
                if (context.isAssignObjectRights()) {
                    componentObjectProfileIdsToCopy = getComponentObjectProfileIdsOfUserType(context.getSourceId());
                }

            } else if (context.getSourceType() == RightAssignationContext.RESOURCE_TYPE.GROUP) {
                spaceProfileIdsToCopy = getDirectSpaceProfileIdsOfGroup(context.getSourceId());
                componentProfileIdsToCopy = getDirectComponentProfileIdsOfGroup(context.getSourceId());
                if (context.isAssignObjectRights()) {
                    componentObjectProfileIdsToCopy = getComponentObjectProfileIdsOfGroupType(
                            context.getSourceId());
                }

            }
            // Loading existing target profile data identifiers
            if (RightAssignationContext.MODE.REPLACE.equals(context.getMode())) {
                if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.USER) {
                    spaceProfileIdsToReplace = getDirectSpaceProfileIdsOfUser(context.getTargetId());
                    componentProfileIdsToReplace = getDirectComponentProfileIdsOfUser(context.getTargetId());

                } else if (context.getTargetType() == RightAssignationContext.RESOURCE_TYPE.GROUP) {
                    spaceProfileIdsToReplace = getDirectSpaceProfileIdsOfGroup(context.getTargetId());
                    componentProfileIdsToReplace = getDirectComponentProfileIdsOfGroup(context.getTargetId());

                }
            }

            // Deleting the current rights of the targeted resource (into the transaction)
            deleteTargetProfiles(context, spaceProfileIdsToReplace, componentProfileIdsToReplace);

            // Adding the new rights for the targeted resource (into the transaction)
            addTargetProfiles(context, spaceProfileIdsToCopy, componentProfileIdsToCopy,
                    componentObjectProfileIdsToCopy);

        } catch (Exception e) {
            cache.resetCache();
            throw new AdminException("Fail to assign rights", e);
        }
    }

    @Override
    public void assignRightsFromUserToUser(RightAssignationContext.MODE operationMode, String sourceUserId,
            String targetUserId, boolean nodeAssignRights, String authorId) throws AdminException {
        RightAssignationContext context = initializeRightAssignationContext(operationMode, nodeAssignRights,
                authorId).fromUserId(sourceUserId).toUserId(targetUserId);
        assignRightsFromSourceToTarget(context);
    }

    @Override
    public void assignRightsFromUserToGroup(RightAssignationContext.MODE operationMode, String sourceUserId,
            String targetGroupId, boolean nodeAssignRights, String authorId) throws AdminException {
        RightAssignationContext context = initializeRightAssignationContext(operationMode, nodeAssignRights,
                authorId).fromUserId(sourceUserId).toGroupId(targetGroupId);
        assignRightsFromSourceToTarget(context);
    }

    @Override
    public void assignRightsFromGroupToUser(RightAssignationContext.MODE operationMode, String sourceGroupId,
            String targetUserId, boolean nodeAssignRights, String authorId) throws AdminException {
        RightAssignationContext context = initializeRightAssignationContext(operationMode, nodeAssignRights,
                authorId).fromGroupId(sourceGroupId).toUserId(targetUserId);
        assignRightsFromSourceToTarget(context);
    }

    @Override
    public void assignRightsFromGroupToGroup(RightAssignationContext.MODE operationMode, String sourceGroupId,
            String targetGroupId, boolean nodeAssignRights, String authorId) throws AdminException {
        RightAssignationContext context = initializeRightAssignationContext(operationMode, nodeAssignRights,
                authorId).fromGroupId(sourceGroupId).toGroupId(targetGroupId);
        assignRightsFromSourceToTarget(context);
    }

    /**
     * Initializing a right assignation context.
     * @param operationMode : value of {@link RightAssignationContext.MODE}
     * @param nodeAssignRights : true if you want also to add rights to nodes
     * @param authorId : the userId of the author of this action
     * @return the initialized {@link RightAssignationContext}
     */
    private RightAssignationContext initializeRightAssignationContext(RightAssignationContext.MODE operationMode,
            boolean nodeAssignRights, String authorId) {
        final RightAssignationContext context;
        if (operationMode == RightAssignationContext.MODE.REPLACE) {
            context = RightAssignationContext.replace();
        } else {
            context = RightAssignationContext.copy();
        }
        if (!nodeAssignRights) {
            context.withoutAssigningComponentObjectRights();
        }
        return context.setAuthor(authorId);
    }

    /**
     * @param userId the user identifier
     * @param domainId the domain identifier
     * @return true if user identified by given userId is the manager of given domain identifier
     */
    @Override
    public boolean isDomainManagerUser(String userId, String domainId) {
        UserDetail userDetail = null;
        try {
            userDetail = getUserDetail(userId);
        } catch (AdminException e) {
            SilverLogger.getLogger(this).error(e);
        }
        return userDetail != null && userDetail.getDomainId().equals(domainId)
                && UserAccessLevel.DOMAIN_ADMINISTRATOR.equals(userDetail.getAccessLevel());
    }

    private static class CheckoutGroupDescriptor {
        private String domainId;
        private GroupDetail[] existingGroups;
        private GroupDetail[] testedGroups;
        private Map<String, GroupDetail> allIncludedGroups;
        private Map<String, String> userIds;
        private String superGroupId;
        private int nbGroupsAdded;
        private int nbGroupsUpdated;

        void addTestedGroupsInAllIncludedGroups() {
            for (GroupDetail testedGroup : testedGroups) {
                allIncludedGroups.put(testedGroup.getSpecificId(), testedGroup);
            }
        }

        public String getDomainId() {
            return domainId;
        }

        public CheckoutGroupDescriptor setDomainId(final String domainId) {
            this.domainId = domainId;
            return this;
        }

        public GroupDetail[] getExistingGroups() {
            return existingGroups;
        }

        public CheckoutGroupDescriptor setExistingGroups(final GroupDetail[] existingGroups) {
            this.existingGroups = existingGroups;
            return this;
        }

        public GroupDetail[] getTestedGroups() {
            return testedGroups;
        }

        public CheckoutGroupDescriptor setTestedGroups(final GroupDetail[] testedGroups) {
            this.testedGroups = testedGroups;
            return this;
        }

        public Map<String, GroupDetail> getAllIncludedGroups() {
            return allIncludedGroups;
        }

        public CheckoutGroupDescriptor setAllIncludedGroups(final Map<String, GroupDetail> allIncludedGroups) {
            this.allIncludedGroups = allIncludedGroups;
            return this;
        }

        public Map<String, String> getUserIds() {
            return userIds;
        }

        public CheckoutGroupDescriptor setUserIds(final Map<String, String> userIds) {
            this.userIds = userIds;
            return this;
        }

        public String getSuperGroupId() {
            return superGroupId;
        }

        public CheckoutGroupDescriptor setSuperGroupId(final String superGroupId) {
            this.superGroupId = superGroupId;
            return this;
        }

        public int getNbGroupsAdded() {
            return nbGroupsAdded;
        }

        public CheckoutGroupDescriptor setNbGroupsAdded(final int nbGroupsAdded) {
            this.nbGroupsAdded = nbGroupsAdded;
            return this;
        }

        public int getNbGroupsUpdated() {
            return nbGroupsUpdated;
        }

        public CheckoutGroupDescriptor setNbGroupsUpdated(final int nbGroupsUpdated) {
            this.nbGroupsUpdated = nbGroupsUpdated;
            return this;
        }
    }
}