org.gluu.oxtrust.ldap.cache.service.CacheRefreshTimer.java Source code

Java tutorial

Introduction

Here is the source code for org.gluu.oxtrust.ldap.cache.service.CacheRefreshTimer.java

Source

/*
 * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
 *
 * Copyright (c) 2014, Gluu
 */

package org.gluu.oxtrust.ldap.cache.service;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.beanutils.BeanUtilsBean2;
import org.apache.commons.io.FilenameUtils;
import org.gluu.oxtrust.config.OxTrustConfiguration;
import org.gluu.oxtrust.ldap.cache.model.CacheCompoundKey;
import org.gluu.oxtrust.ldap.cache.model.GluuInumMap;
import org.gluu.oxtrust.ldap.cache.model.GluuSimplePerson;
import org.gluu.oxtrust.ldap.service.ApplianceService;
import org.gluu.oxtrust.ldap.service.AttributeService;
import org.gluu.oxtrust.ldap.service.InumService;
import org.gluu.oxtrust.ldap.service.PersonService;
import org.gluu.oxtrust.model.GluuAppliance;
import org.gluu.oxtrust.model.GluuCustomAttribute;
import org.gluu.oxtrust.model.GluuCustomPerson;
import org.gluu.oxtrust.service.external.ExternalCacheRefreshService;
import org.gluu.oxtrust.util.OxTrustConstants;
import org.gluu.oxtrust.util.PropertyUtil;
import org.gluu.site.ldap.LDAPConnectionProvider;
import org.gluu.site.ldap.OperationsFacade;
import org.gluu.site.ldap.persistence.LdapEntryManager;
import org.gluu.site.ldap.persistence.exception.EntryPersistenceException;
import org.gluu.site.ldap.persistence.exception.LdapMappingException;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.async.Asynchronous;
import org.jboss.seam.async.TimerSchedule;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Log;
import org.xdi.config.oxtrust.ApplicationConfiguration;
import org.xdi.config.oxtrust.CacheRefreshAttributeMapping;
import org.xdi.config.oxtrust.CacheRefreshConfiguration;
import org.xdi.ldap.model.GluuBoolean;
import org.xdi.ldap.model.GluuDummyEntry;
import org.xdi.ldap.model.GluuStatus;
import org.xdi.model.SimpleProperty;
import org.xdi.model.ldap.GluuLdapConfiguration;
import org.xdi.service.ObjectSerializationService;
import org.xdi.service.SchemaService;
import org.xdi.util.ArrayHelper;
import org.xdi.util.Pair;
import org.xdi.util.StringHelper;
import org.xdi.util.security.PropertiesDecrypter;

import com.unboundid.ldap.sdk.Filter;

/**
 * Check periodically if source servers contains updates and trigger target
 * server entry update if needed
 * 
 * @author Yuriy Movchan Date: 05.05.2011
 */
@Name("cacheRefreshTimer")
@Scope(ScopeType.APPLICATION)
@AutoCreate
@Startup(depends = { "appInitializer", "oxTrustConfiguration", "cacheRefreshSnapshotFileService" })
public class CacheRefreshTimer {

    @Logger
    Log log;

    private static final String LETTERS_FOR_SEARCH = "abcdefghijklmnopqrstuvwxyz1234567890.";
    private static final String[] TARGET_PERSON_RETURN_ATTRIBUTES = { OxTrustConstants.inum };

    @In
    protected AttributeService attributeService;

    @In(value = "oxTrustConfiguration")
    private OxTrustConfiguration oxTrustConfiguration;

    @In
    private CacheRefreshService cacheRefreshService;

    @In
    private PersonService personService;

    @In
    private LdapEntryManager ldapEntryManager;

    @In
    private ApplianceService applianceService;

    @In
    private CacheRefreshSnapshotFileService cacheRefreshSnapshotFileService;

    @In
    private ExternalCacheRefreshService externalCacheRefreshService;

    @In
    private SchemaService schemaService;

    @In
    private InumService inumService;

    @In(value = "#{oxTrustConfiguration.applicationConfiguration}")
    private ApplicationConfiguration applicationConfiguration;

    @In(value = "#{oxTrustConfiguration.cryptoConfigurationSalt}")
    private String cryptoConfigurationSalt;

    @In
    private ObjectSerializationService objectSerializationService;

    private AtomicBoolean isActive;
    private long lastFinishedTime;

    @Observer("org.jboss.seam.postInitialization")
    public void init() {
        log.info("Initializing CacheRefreshTimer...");
        this.isActive = new AtomicBoolean(false);
        this.lastFinishedTime = System.currentTimeMillis();

        // Clean up previous Inum cache
        CacheRefreshConfiguration cacheRefreshConfiguration = oxTrustConfiguration.getCacheRefreshConfiguration();
        if (cacheRefreshConfiguration != null) {
            String snapshotFolder = cacheRefreshConfiguration.getSnapshotFolder();
            if (StringHelper.isNotEmpty(snapshotFolder)) {
                String inumCachePath = getInumCachePath(cacheRefreshConfiguration);
                objectSerializationService.cleanup(inumCachePath);
            }
        }

        // Schedule to start cache refresh every 1 minute
        Events.instance().raiseTimedEvent(OxTrustConstants.EVENT_CACHE_REFRESH_TIMER,
                new TimerSchedule(1 * 60 * 1000L, 1 * 60 * 1000L));
    }

    @Observer(OxTrustConstants.EVENT_CACHE_REFRESH_TIMER)
    @Asynchronous
    public void process() {
        if (this.isActive.get()) {
            log.debug("Another process is active");
            return;
        }

        CacheRefreshConfiguration cacheRefreshConfiguration = oxTrustConfiguration.getCacheRefreshConfiguration();

        if (!this.isActive.compareAndSet(false, true)) {
            log.debug("Failed to start process exclusively");
            return;
        }

        try {
            GluuAppliance currentAppliance = applianceService.getAppliance();
            if (!isStartCacheRefresh(cacheRefreshConfiguration, currentAppliance)) {
                log.debug("Starting conditions aren't reached");
                return;
            }

            processImpl(cacheRefreshConfiguration, currentAppliance);
            updateApplianceStatus(currentAppliance, System.currentTimeMillis());

            this.lastFinishedTime = System.currentTimeMillis();
        } catch (Throwable ex) {
            log.error("Exception happened while executing cache refresh synchronization", ex);
        } finally {
            log.debug("Allowing to run new process exclusively");
            this.isActive.set(false);
        }
    }

    private boolean isStartCacheRefresh(CacheRefreshConfiguration cacheRefreshConfiguration,
            GluuAppliance currentAppliance) {
        if (!GluuBoolean.ENABLED.equals(currentAppliance.getVdsCacheRefreshEnabled())) {
            return false;
        }

        long poolingInterval = StringHelper.toInteger(currentAppliance.getVdsCacheRefreshPollingInterval()) * 60
                * 1000;
        if (poolingInterval < 0) {
            return false;
        }

        String cacheRefreshServerIpAddress = currentAppliance.getCacheRefreshServerIpAddress();
        if (StringHelper.isEmpty(cacheRefreshServerIpAddress)) {
            log.debug("There is no master Cache Refresh server");
            return false;
        }

        // Compare server IP address with cacheRefreshServerIp
        boolean cacheRefreshServer = false;
        try {
            Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
            for (NetworkInterface networkInterface : Collections.list(nets)) {
                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                for (InetAddress inetAddress : Collections.list(inetAddresses)) {
                    if (StringHelper.equals(cacheRefreshServerIpAddress, inetAddress.getHostAddress())) {
                        cacheRefreshServer = true;
                        break;
                    }
                }

                if (cacheRefreshServer) {
                    break;
                }
            }
        } catch (SocketException ex) {
            log.error("Failed to enumerate server IP addresses", ex);
        }

        if (!cacheRefreshServer) {
            log.debug("This server isn't master Cache Refresh server");
            return false;
        }

        // Check if cache refresh specific configuration was loaded
        if (cacheRefreshConfiguration == null) {
            log.info(
                    "Failed to start cache refresh. Can't loading configuration from oxTrustCacheRefresh.properties");
            return false;
        }

        long timeDiffrence = System.currentTimeMillis() - this.lastFinishedTime;

        return timeDiffrence >= poolingInterval;
    }

    private void processImpl(CacheRefreshConfiguration cacheRefreshConfiguration, GluuAppliance currentAppliance) {
        CacheRefreshUpdateMethod updateMethod = getUpdateMethod(cacheRefreshConfiguration);

        // Prepare and check connections to LDAP servers
        LdapServerConnection[] sourceServerConnections = prepareLdapServerConnections(cacheRefreshConfiguration,
                cacheRefreshConfiguration.getSourceConfigs());
        LdapServerConnection inumDbServerConnection = prepareLdapServerConnection(cacheRefreshConfiguration,
                cacheRefreshConfiguration.getInumConfig());

        boolean isVdsUpdate = CacheRefreshUpdateMethod.VDS.equals(updateMethod);
        LdapServerConnection targetServerConnection = null;
        if (isVdsUpdate) {
            targetServerConnection = prepareLdapServerConnection(cacheRefreshConfiguration,
                    cacheRefreshConfiguration.getTargetConfig());
        }

        try {
            if ((sourceServerConnections == null) || (inumDbServerConnection == null)
                    || (isVdsUpdate && (targetServerConnection == null))) {
                log.error("Skipping cache refresh due to invalid server configuration");
            } else {
                detectChangedEntries(cacheRefreshConfiguration, currentAppliance, sourceServerConnections,
                        inumDbServerConnection, targetServerConnection, updateMethod);
            }
        } finally {
            // Close connections to LDAP servers
            try {
                closeLdapServerConnection(sourceServerConnections);
            } catch (Exception e) {
                // Nothing can be done
            }
            try {
                closeLdapServerConnection(inumDbServerConnection);
            } catch (Exception e) {
                // Nothing can be done
            }
            try {
                if (isVdsUpdate) {
                    closeLdapServerConnection(targetServerConnection);
                }
            } catch (Exception e) {
                // Nothing can be done
            }
        }

        return;
    }

    @SuppressWarnings("unchecked")
    private boolean detectChangedEntries(CacheRefreshConfiguration cacheRefreshConfiguration,
            GluuAppliance currentAppliance, LdapServerConnection[] sourceServerConnections,
            LdapServerConnection inumDbServerConnection, LdapServerConnection targetServerConnection,
            CacheRefreshUpdateMethod updateMethod) {
        boolean isVDSMode = CacheRefreshUpdateMethod.VDS.equals(updateMethod);

        // Load all entries from Source servers
        log.info("Attempting to load entries from source server");
        List<GluuSimplePerson> sourcePersons;

        if (cacheRefreshConfiguration.isUseSearchLimit()) {
            sourcePersons = loadSourceServerEntries(cacheRefreshConfiguration, sourceServerConnections);
        } else {
            sourcePersons = loadSourceServerEntriesWithoutLimits(cacheRefreshConfiguration,
                    sourceServerConnections);
        }

        log.info("Found '{0}' entries in source server", sourcePersons.size());

        Map<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyMap = getSourcePersonCompoundKeyMap(
                cacheRefreshConfiguration, sourcePersons);
        log.info("Found '{0}' unique entries in source server", sourcePersonCacheCompoundKeyMap.size());

        // Load all inum entries
        List<GluuInumMap> inumMaps = null;

        // Load all inum entries from local disk cache
        String inumCachePath = getInumCachePath(cacheRefreshConfiguration);
        Object loadedObject = objectSerializationService.loadObject(inumCachePath);
        if (loadedObject != null) {
            try {
                inumMaps = (List<GluuInumMap>) loadedObject;
                log.debug("Found '{0}' entries in inum objects disk cache", inumMaps.size());
            } catch (Exception ex) {
                log.error("Failed to convert to GluuInumMap list", ex);
                objectSerializationService.cleanup(inumCachePath);
            }
        }

        if (inumMaps == null) {
            // Load all inum entries from LDAP
            inumMaps = loadInumServerEntries(cacheRefreshConfiguration, inumDbServerConnection);
            log.info("Found '{0}' entries in inum server", inumMaps.size());
        }

        HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap = getPrimaryKeyAttrValueInumMap(inumMaps);

        // Go through Source entries and create new InumMap entries if needed
        HashMap<CacheCompoundKey, GluuInumMap> addedPrimaryKeyAttrValueInumMap = addNewInumServerEntries(
                cacheRefreshConfiguration, inumDbServerConnection, sourcePersonCacheCompoundKeyMap,
                primaryKeyAttrValueInumMap);

        HashMap<CacheCompoundKey, GluuInumMap> allPrimaryKeyAttrValueInumMap = getAllInumServerEntries(
                primaryKeyAttrValueInumMap, addedPrimaryKeyAttrValueInumMap);
        log.debug("Count actual inum entries '{0}' after updating inum server",
                allPrimaryKeyAttrValueInumMap.size());

        HashMap<String, Integer> currInumWithEntryHashCodeMap = getSourcePersonsHashCodesMap(inumDbServerConnection,
                sourcePersonCacheCompoundKeyMap, allPrimaryKeyAttrValueInumMap);
        log.debug("Count actual source entries '{0}' after calculating hash code",
                currInumWithEntryHashCodeMap.size());

        // Create snapshots cache folder if needed
        boolean result = cacheRefreshSnapshotFileService.prepareSnapshotsFolder(cacheRefreshConfiguration);
        if (!result) {
            return false;
        }

        // Load last snapshot into memory
        Map<String, Integer> prevInumWithEntryHashCodeMap = cacheRefreshSnapshotFileService
                .readLastSnapshot(cacheRefreshConfiguration);

        // Compare 2 snapshot and invoke update if needed
        Set<String> changedInums = getChangedInums(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap,
                isVDSMode);
        log.info("Found '{0}' changed entries", changedInums.size());

        // Load problem list from disk and add to changedInums
        List<String> problemInums = cacheRefreshSnapshotFileService.readProblemList(cacheRefreshConfiguration);
        if (problemInums != null) {
            log.info("Loaded '{0}' problem entries from problem file", problemInums.size());
            // Process inums from problem list too
            changedInums.addAll(problemInums);
        }

        List<String> updatedInums = null;
        if (isVDSMode) {
            // Update request to VDS to update entries on target server
            updatedInums = updateTargetEntriesViaVDS(cacheRefreshConfiguration, targetServerConnection,
                    changedInums);
        } else {
            updatedInums = updateTargetEntriesViaCopy(cacheRefreshConfiguration, sourcePersonCacheCompoundKeyMap,
                    allPrimaryKeyAttrValueInumMap, changedInums);
        }

        log.info("Updated '{0}' entries", updatedInums.size());
        changedInums.removeAll(updatedInums);
        log.info("Failed to update '{0}' entries", changedInums.size());

        // Persist snapshot to cache folder
        result = cacheRefreshSnapshotFileService.createSnapshot(cacheRefreshConfiguration,
                currInumWithEntryHashCodeMap);
        if (!result) {
            return false;
        }

        // Retain only specified number of snapshots
        cacheRefreshSnapshotFileService.retainSnapshots(cacheRefreshConfiguration,
                cacheRefreshConfiguration.getSnapshotMaxCount());

        // Save changedInums as problem list to disk
        currentAppliance.setVdsCacheRefreshProblemCount(String.valueOf(changedInums.size()));
        cacheRefreshSnapshotFileService.writeProblemList(cacheRefreshConfiguration, changedInums);

        // Prepare list of persons for removal
        List<GluuSimplePerson> personsForRemoval = null;

        boolean keepExternalPerson = cacheRefreshConfiguration.isKeepExternalPerson();
        log.debug("Keep external persons: '{0}'", keepExternalPerson);
        if (keepExternalPerson) {
            // Determine entries which need to remove
            personsForRemoval = getRemovedPersons(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap);
        } else {
            // Process entries which don't exist in source server

            // Load all entries from Target server
            List<GluuSimplePerson> targetPersons = loadTargetServerEntries(cacheRefreshConfiguration,
                    ldapEntryManager);
            log.info("Found '{0}' entries in target server", targetPersons.size());

            // Detect entries which need to remove
            personsForRemoval = processTargetPersons(targetPersons, currInumWithEntryHashCodeMap);
        }
        log.debug("Count entries '{0}' for removal from target server", personsForRemoval.size());

        // Remove entries from target server
        HashMap<String, GluuInumMap> inumInumMap = getInumInumMap(inumMaps);
        Pair<List<String>, List<String>> removeTargetEntriesResult = removeTargetEntries(inumDbServerConnection,
                ldapEntryManager, personsForRemoval, inumInumMap);
        List<String> removedPersonInums = removeTargetEntriesResult.getFirst();
        List<String> removedGluuInumMaps = removeTargetEntriesResult.getSecond();
        log.info("Removed '{0}' persons from target server", removedPersonInums.size());

        // Prepare list of inum for serialization
        ArrayList<GluuInumMap> currentInumMaps = applyChangesToInumMap(inumInumMap, addedPrimaryKeyAttrValueInumMap,
                removedGluuInumMaps);

        // Strore all inum entries into local disk cache
        objectSerializationService.saveObject(inumCachePath, currentInumMaps);

        currentAppliance
                .setVdsCacheRefreshLastUpdateCount(String.valueOf(updatedInums.size() + removedPersonInums.size()));

        return true;
    }

    private ArrayList<GluuInumMap> applyChangesToInumMap(HashMap<String, GluuInumMap> inumInumMap,
            HashMap<CacheCompoundKey, GluuInumMap> addedPrimaryKeyAttrValueInumMap,
            List<String> removedGluuInumMaps) {
        log.info("There are '{0}' entries before updating inum list", inumInumMap.size());
        for (String removedGluuInumMap : removedGluuInumMaps) {
            inumInumMap.remove(removedGluuInumMap);
        }
        log.info("There are '{0}' entries after removal '{1}' entries", inumInumMap.size(),
                removedGluuInumMaps.size());

        ArrayList<GluuInumMap> currentInumMaps = new ArrayList<GluuInumMap>(inumInumMap.values());
        currentInumMaps.addAll(addedPrimaryKeyAttrValueInumMap.values());
        log.info("There are '{0}' entries after adding '{1}' entries", currentInumMaps.size(),
                addedPrimaryKeyAttrValueInumMap.size());

        return currentInumMaps;
    }

    private Set<String> getChangedInums(HashMap<String, Integer> currInumWithEntryHashCodeMap,
            Map<String, Integer> prevInumWithEntryHashCodeMap, boolean includeDeleted) {
        // Find chaged inums
        Set<String> changedInums = null;
        // First time run
        if (prevInumWithEntryHashCodeMap == null) {
            changedInums = new HashSet<String>(currInumWithEntryHashCodeMap.keySet());
        } else {
            changedInums = new HashSet<String>();

            // Add all inums which not exist in new snapshot
            if (includeDeleted) {
                for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) {
                    if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) {
                        changedInums.add(prevInumKey);
                    }
                }
            }

            // Add all new inums and changed inums
            for (Entry<String, Integer> currEntry : currInumWithEntryHashCodeMap.entrySet()) {
                String currInumKey = currEntry.getKey();
                Integer prevHashCode = prevInumWithEntryHashCodeMap.get(currInumKey);
                if ((prevHashCode == null)
                        || ((prevHashCode != null) && !(prevHashCode.equals(currEntry.getValue())))) {
                    changedInums.add(currInumKey);
                }
            }
        }
        return changedInums;
    }

    private List<GluuSimplePerson> getRemovedPersons(HashMap<String, Integer> currInumWithEntryHashCodeMap,
            Map<String, Integer> prevInumWithEntryHashCodeMap) {
        // First time run
        if (prevInumWithEntryHashCodeMap == null) {
            return new ArrayList<GluuSimplePerson>(0);
        }

        // Add all inums which not exist in new snapshot
        Set<String> deletedInums = new HashSet<String>();
        for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) {
            if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) {
                deletedInums.add(prevInumKey);
            }
        }

        List<GluuSimplePerson> deletedPersons = new ArrayList<GluuSimplePerson>(deletedInums.size());
        for (String deletedInum : deletedInums) {
            GluuSimplePerson person = new GluuSimplePerson();
            String personDn = personService.getDnForPerson(deletedInum);
            person.setDn(personDn);

            List<GluuCustomAttribute> customAttributes = new ArrayList<GluuCustomAttribute>();
            customAttributes.add(new GluuCustomAttribute(OxTrustConstants.inum, deletedInum));
            person.setCustomAttributes(customAttributes);

            deletedPersons.add(person);
        }

        return deletedPersons;
    }

    private List<String> updateTargetEntriesViaVDS(CacheRefreshConfiguration cacheRefreshConfiguration,
            LdapServerConnection targetServerConnection, Set<String> changedInums) {
        List<String> result = new ArrayList<String>();

        LdapEntryManager targetLdapEntryManager = targetServerConnection.getLdapEntryManager();
        Filter filter = cacheRefreshService.createObjectClassPresenceFilter();
        for (String changedInum : changedInums) {
            String baseDn = "action=synchronizecache," + personService.getDnForPerson(changedInum);
            try {
                targetLdapEntryManager.findEntries(baseDn, GluuDummyEntry.class, filter, null,
                        cacheRefreshConfiguration.getLdapSearchSizeLimit());
                result.add(changedInum);
                log.debug("Updated entry with inum {0}", changedInum);
            } catch (LdapMappingException ex) {
                log.error("Failed to update entry with inum '{0}' using baseDN {1}", ex, changedInum, baseDn);
            }
        }

        return result;
    }

    private List<String> updateTargetEntriesViaCopy(CacheRefreshConfiguration cacheRefreshConfiguration,
            Map<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyMap,
            HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap, Set<String> changedInums) {
        HashMap<String, CacheCompoundKey> inumCacheCompoundKeyMap = getInumCacheCompoundKeyMap(
                primaryKeyAttrValueInumMap);
        Map<String, String> targetServerAttributesMapping = getTargetServerAttributesMapping(
                cacheRefreshConfiguration);
        String[] customObjectClasses = applicationConfiguration.getPersonObjectClassTypes();

        List<String> result = new ArrayList<String>();

        if (!validateTargetServerSchema(cacheRefreshConfiguration, targetServerAttributesMapping,
                customObjectClasses)) {
            return result;
        }

        for (String targetInum : changedInums) {
            CacheCompoundKey compoundKey = inumCacheCompoundKeyMap.get(targetInum);
            if (compoundKey == null) {
                continue;
            }

            GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyMap.get(compoundKey);
            if (sourcePerson == null) {
                continue;
            }

            if (updateTargetEntryViaCopy(sourcePerson, targetInum, customObjectClasses,
                    targetServerAttributesMapping)) {
                result.add(targetInum);
            }
        }

        return result;
    }

    private boolean validateTargetServerSchema(CacheRefreshConfiguration cacheRefreshConfiguration,
            Map<String, String> targetServerAttributesMapping, String[] customObjectClasses) {
        // Get list of return attributes
        String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration);
        String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration);
        String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes);

        GluuSimplePerson sourcePerson = new GluuSimplePerson();
        for (String returnAttribute : returnAttributes) {
            sourcePerson.setAttribute(returnAttribute, "Test");
        }

        String targetInum = inumService.generateInums(OxTrustConstants.INUM_TYPE_PEOPLE_SLUG, false);
        String targetPersonDn = personService.getDnForPerson(targetInum);

        GluuCustomPerson targetPerson = new GluuCustomPerson();
        targetPerson.setDn(targetPersonDn);
        targetPerson.setInum(targetInum);
        targetPerson.setStatus(GluuStatus.ACTIVE);
        targetPerson.setCustomObjectClasses(customObjectClasses);

        // Update list of return attributes according mapping
        cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson);

        // Execute interceptor script
        externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson);
        boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson);
        if (!executionResult) {
            log.error("Failed to execute Cache Refresh scripts for person '{0}'", targetInum);
            return false;
        }

        // Validate target server attributes
        List<GluuCustomAttribute> customAttributes = targetPerson.getCustomAttributes();

        List<String> targetAttributes = new ArrayList<String>(customAttributes.size());
        for (GluuCustomAttribute customAttribute : customAttributes) {
            targetAttributes.add(customAttribute.getName());
        }

        List<String> targetObjectClasses = Arrays
                .asList(ldapEntryManager.getObjectClasses(targetPerson, GluuCustomPerson.class));

        return validateTargetServerSchema(targetObjectClasses, targetAttributes);
    }

    private boolean validateTargetServerSchema(List<String> targetObjectClasses, List<String> targetAttributes) {
        Set<String> objectClassesAttributesSet = schemaService.getObjectClassesAttributes(schemaService.getSchema(),
                targetObjectClasses.toArray(new String[0]));

        Set<String> targetAttributesSet = new LinkedHashSet<String>();
        for (String attrbute : targetAttributes) {
            targetAttributesSet.add(StringHelper.toLowerCase(attrbute));
        }

        targetAttributesSet.removeAll(objectClassesAttributesSet);

        if (targetAttributesSet.size() == 0) {
            return true;
        }

        log.error("Skipping target entries update. Destination server schema doesn't has next attributes: '{0}'",
                targetAttributesSet);

        return false;
    }

    private boolean updateTargetEntryViaCopy(GluuSimplePerson sourcePerson, String targetInum,
            String[] targetCustomObjectClasses, Map<String, String> targetServerAttributesMapping) {
        String targetPersonDn = personService.getDnForPerson(targetInum);
        GluuCustomPerson targetPerson = null;
        boolean updatePerson;
        if (personService.contains(targetPersonDn)) {
            try {
                targetPerson = personService.findPersonByDn(targetPersonDn);
                log.debug("Found person by inum '{0}'", targetInum);
            } catch (EntryPersistenceException ex) {
                log.error("Failed to find person '{0}'", ex, targetInum);
                return false;
            }
            updatePerson = true;
        } else {
            targetPerson = new GluuCustomPerson();
            targetPerson.setDn(targetPersonDn);
            targetPerson.setInum(targetInum);
            targetPerson.setStatus(GluuStatus.ACTIVE);
            updatePerson = false;
        }
        targetPerson.setCustomObjectClasses(targetCustomObjectClasses);

        targetPerson.setSourceServerName(sourcePerson.getSourceServerName());

        cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson);

        // Execute interceptor script
        boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson);
        if (!executionResult) {
            log.error("Failed to execute Cache Refresh scripts for person '{0}'", targetInum);
            return false;
        }

        try {
            if (updatePerson) {
                personService.updatePerson(targetPerson);
                log.debug("Updated person '{0}'", targetInum);
            } else {
                personService.addPerson(targetPerson);
                log.debug("Added new person '{0}'", targetInum);
            }
        } catch (Exception ex) {
            log.error("Failed to '{0}' person '{1}'", ex, updatePerson ? "update" : "add", targetInum);
            return false;
        }

        return true;
    }

    private HashMap<String, CacheCompoundKey> getInumCacheCompoundKeyMap(
            HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap) {
        HashMap<String, CacheCompoundKey> result = new HashMap<String, CacheCompoundKey>();

        for (Entry<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMapEntry : primaryKeyAttrValueInumMap
                .entrySet()) {
            result.put(primaryKeyAttrValueInumMapEntry.getValue().getInum(),
                    primaryKeyAttrValueInumMapEntry.getKey());
        }

        return result;
    }

    private Pair<List<String>, List<String>> removeTargetEntries(LdapServerConnection inumDbServerConnection,
            LdapEntryManager targetLdapEntryManager, List<GluuSimplePerson> removedPersons,
            HashMap<String, GluuInumMap> inumInumMap) {

        String runDate = ldapEntryManager.encodeGeneralizedTime(new Date(this.lastFinishedTime));

        LdapEntryManager inumDbLdapEntryManager = inumDbServerConnection.getLdapEntryManager();
        List<String> result1 = new ArrayList<String>();
        List<String> result2 = new ArrayList<String>();

        for (GluuSimplePerson removedPerson : removedPersons) {
            String inum = removedPerson.getAttribute(OxTrustConstants.inum);

            // Update GluuInumMap if it exist
            GluuInumMap currentInumMap = inumInumMap.get(inum);
            if (currentInumMap == null) {
                log.warn("Can't find inum entry of person with DN: {0}", removedPerson.getDn());
            } else {
                GluuInumMap removedInumMap = getMarkInumMapEntryAsRemoved(currentInumMap, runDate);
                try {
                    inumDbLdapEntryManager.merge(removedInumMap);
                    result2.add(removedInumMap.getInum());
                } catch (LdapMappingException ex) {
                    log.error("Failed to update entry with inum '{0}' and DN: {1}", ex, currentInumMap.getInum(),
                            currentInumMap.getDn());
                    continue;
                }
            }

            // Remove person from target server
            try {
                targetLdapEntryManager.removeWithSubtree(removedPerson.getDn());
                result1.add(inum);
            } catch (LdapMappingException ex) {
                log.error("Failed to remove person entry with inum '{0}' and DN: {1}", ex, inum,
                        removedPerson.getDn());
                continue;
            }

            log.debug("Person with DN: '{0}' removed from target server", removedPerson.getDn());
        }

        return new Pair<List<String>, List<String>>(result1, result2);
    }

    private GluuInumMap getMarkInumMapEntryAsRemoved(GluuInumMap currentInumMap, String date) {
        GluuInumMap clonedInumMap;
        try {
            clonedInumMap = (GluuInumMap) BeanUtilsBean2.getInstance().cloneBean(currentInumMap);
        } catch (Exception ex) {
            log.error("Failed to prepare GluuInumMap for removal", ex);
            return null;
        }

        String suffix = "-" + date;

        String[] primaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getPrimaryKeyValues());
        String[] secondaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getSecondaryKeyValues());
        String[] tertiaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getTertiaryKeyValues());

        if (ArrayHelper.isNotEmpty(primaryKeyValues)) {
            markInumMapEntryKeyValuesAsRemoved(primaryKeyValues, suffix);
        }

        if (ArrayHelper.isNotEmpty(secondaryKeyValues)) {
            markInumMapEntryKeyValuesAsRemoved(secondaryKeyValues, suffix);
        }

        if (ArrayHelper.isNotEmpty(tertiaryKeyValues)) {
            markInumMapEntryKeyValuesAsRemoved(tertiaryKeyValues, suffix);
        }

        clonedInumMap.setPrimaryKeyValues(primaryKeyValues);
        clonedInumMap.setSecondaryKeyValues(secondaryKeyValues);
        clonedInumMap.setTertiaryKeyValues(tertiaryKeyValues);

        clonedInumMap.setStatus(GluuStatus.INACTIVE);

        return clonedInumMap;
    }

    private void markInumMapEntryKeyValuesAsRemoved(String[] keyValues, String suffix) {
        for (int i = 0; i < keyValues.length; i++) {
            keyValues[i] = keyValues[i] + suffix;
        }
    }

    private List<GluuInumMap> loadInumServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration,
            LdapServerConnection inumDbServerConnection) {
        LdapEntryManager inumDbldapEntryManager = inumDbServerConnection.getLdapEntryManager();
        String inumbaseDn = inumDbServerConnection.getBaseDns()[0];

        Filter filterObjectClass = Filter.createEqualityFilter(OxTrustConstants.objectClass,
                OxTrustConstants.objectClassInumMap);
        Filter filterStatus = Filter.createNOTFilter(
                Filter.createEqualityFilter(OxTrustConstants.gluuStatus, GluuStatus.INACTIVE.getValue()));
        Filter filter = Filter.createANDFilter(filterObjectClass, filterStatus);

        return inumDbldapEntryManager.findEntries(inumbaseDn, GluuInumMap.class, filter, null,
                cacheRefreshConfiguration.getLdapSearchSizeLimit());
    }

    private List<GluuSimplePerson> loadSourceServerEntriesWithoutLimits(
            CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection[] sourceServerConnections) {
        Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter());
        String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration);
        String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration);
        String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration);
        String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration);

        String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes);

        Set<String> addedDns = new HashSet<String>();

        List<GluuSimplePerson> sourcePersons = new ArrayList<GluuSimplePerson>();
        for (LdapServerConnection sourceServerConnection : sourceServerConnections) {
            String sourceServerName = sourceServerConnection.getSourceServerName();

            LdapEntryManager sourceLdapEntryManager = sourceServerConnection.getLdapEntryManager();
            String[] baseDns = sourceServerConnection.getBaseDns();
            Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, "", customFilter);
            if (log.isTraceEnabled()) {
                log.trace("Using next filter to load entris from source server: {0}", filter);
            }

            for (String baseDn : baseDns) {
                List<GluuSimplePerson> currentSourcePersons = sourceLdapEntryManager.findEntries(baseDn,
                        GluuSimplePerson.class, filter, returnAttributes,
                        cacheRefreshConfiguration.getLdapSearchSizeLimit());

                // Add to result and ignore root entry if needed
                for (GluuSimplePerson currentSourcePerson : currentSourcePersons) {
                    currentSourcePerson.setSourceServerName(sourceServerName);
                    // if (!StringHelper.equalsIgnoreCase(baseDn,
                    // currentSourcePerson.getDn())) {
                    String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase();
                    if (!addedDns.contains(currentSourcePersonDn)) {
                        sourcePersons.add(currentSourcePerson);
                        addedDns.add(currentSourcePersonDn);
                    }
                    // }
                }
            }
        }

        return sourcePersons;
    }

    private List<GluuSimplePerson> loadSourceServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration,
            LdapServerConnection[] sourceServerConnections) {
        Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter());
        String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration);
        String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration);
        String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration);
        String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration);

        String[] twoLettersArray = createTwoLettersArray();
        String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes);

        Set<String> addedDns = new HashSet<String>();

        List<GluuSimplePerson> sourcePersons = new ArrayList<GluuSimplePerson>();
        for (LdapServerConnection sourceServerConnection : sourceServerConnections) {
            String sourceServerName = sourceServerConnection.getSourceServerName();

            LdapEntryManager sourceLdapEntryManager = sourceServerConnection.getLdapEntryManager();
            String[] baseDns = sourceServerConnection.getBaseDns();
            for (String keyAttributeStart : twoLettersArray) {
                Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, keyAttributeStart,
                        customFilter);
                if (log.isDebugEnabled()) {
                    log.trace("Using next filter to load entris from source server: {0}", filter);
                }

                for (String baseDn : baseDns) {
                    List<GluuSimplePerson> currentSourcePersons = sourceLdapEntryManager.findEntries(baseDn,
                            GluuSimplePerson.class, filter, returnAttributes,
                            cacheRefreshConfiguration.getLdapSearchSizeLimit());

                    // Add to result and ignore root entry if needed
                    for (GluuSimplePerson currentSourcePerson : currentSourcePersons) {
                        currentSourcePerson.setSourceServerName(sourceServerName);
                        // if (!StringHelper.equalsIgnoreCase(baseDn,
                        // currentSourcePerson.getDn())) {
                        String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase();
                        if (!addedDns.contains(currentSourcePersonDn)) {
                            sourcePersons.add(currentSourcePerson);
                            addedDns.add(currentSourcePersonDn);
                        }
                        // }
                    }
                }
            }
        }

        return sourcePersons;
    }

    private List<GluuSimplePerson> loadTargetServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration,
            LdapEntryManager targetLdapEntryManager) {
        Filter filter = Filter.createEqualityFilter(OxTrustConstants.objectClass,
                OxTrustConstants.objectClassPerson);

        return targetLdapEntryManager.findEntries(personService.getDnForPerson(null), GluuSimplePerson.class,
                filter, TARGET_PERSON_RETURN_ATTRIBUTES, cacheRefreshConfiguration.getLdapSearchSizeLimit());
    }

    private GluuInumMap addGluuInumMap(String inumbBaseDn, LdapEntryManager inumDbLdapEntryManager,
            String[] primaryKeyAttrName, String[][] primaryKeyValues) {
        String inum = cacheRefreshService.generateInumForNewInumMap(inumbBaseDn, inumDbLdapEntryManager);
        String inumDn = cacheRefreshService.getDnForInum(inumbBaseDn, inum);

        GluuInumMap inumMap = new GluuInumMap();
        inumMap.setDn(inumDn);
        inumMap.setInum(inum);
        inumMap.setPrimaryKeyAttrName(primaryKeyAttrName[0]);
        inumMap.setPrimaryKeyValues(primaryKeyValues[0]);
        if (primaryKeyAttrName.length > 1) {
            inumMap.setSecondaryKeyAttrName(primaryKeyAttrName[1]);
            inumMap.setSecondaryKeyValues(primaryKeyValues[1]);
        }
        if (primaryKeyAttrName.length > 2) {
            inumMap.setTertiaryKeyAttrName(primaryKeyAttrName[2]);
            inumMap.setTertiaryKeyValues(primaryKeyValues[2]);
        }
        inumMap.setStatus(GluuStatus.ACTIVE);
        cacheRefreshService.addInumMap(inumDbLdapEntryManager, inumMap);

        return inumMap;
    }

    private HashMap<CacheCompoundKey, GluuInumMap> addNewInumServerEntries(
            CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection inumDbServerConnection,
            Map<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyMap,
            HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap) {
        LdapEntryManager inumDbLdapEntryManager = inumDbServerConnection.getLdapEntryManager();
        String inumbaseDn = inumDbServerConnection.getBaseDns()[0];

        HashMap<CacheCompoundKey, GluuInumMap> result = new HashMap<CacheCompoundKey, GluuInumMap>();

        String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration);
        for (Entry<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap
                .entrySet()) {
            CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey();
            GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue();

            if (log.isTraceEnabled()) {
                log.trace("Checking source entry with key: '{0}', and DN: {1}", cacheCompoundKey,
                        sourcePerson.getDn());
            }

            GluuInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey);
            if (currentInumMap == null) {
                String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson);
                currentInumMap = addGluuInumMap(inumbaseDn, inumDbLdapEntryManager, keyAttributesWithoutValues,
                        keyAttributesValues);
                result.put(cacheCompoundKey, currentInumMap);
                log.debug("Added new inum entry for DN: {0}", sourcePerson.getDn());
            } else {
                log.trace("Inum entry for DN: '{0}' exist", sourcePerson.getDn());
            }
        }

        return result;
    }

    private HashMap<CacheCompoundKey, GluuInumMap> getAllInumServerEntries(
            HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap,
            HashMap<CacheCompoundKey, GluuInumMap> addedPrimaryKeyAttrValueInumMap) {
        HashMap<CacheCompoundKey, GluuInumMap> result = new HashMap<CacheCompoundKey, GluuInumMap>();

        result.putAll(primaryKeyAttrValueInumMap);
        result.putAll(addedPrimaryKeyAttrValueInumMap);

        return result;
    }

    private HashMap<String, Integer> getSourcePersonsHashCodesMap(LdapServerConnection inumDbServerConnection,
            Map<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyMap,
            HashMap<CacheCompoundKey, GluuInumMap> primaryKeyAttrValueInumMap) {
        LdapEntryManager inumDbLdapEntryManager = inumDbServerConnection.getLdapEntryManager();

        HashMap<String, Integer> result = new HashMap<String, Integer>();

        for (Entry<CacheCompoundKey, GluuSimplePerson> sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap
                .entrySet()) {
            CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey();
            GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue();

            GluuInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey);

            result.put(currentInumMap.getInum(), inumDbLdapEntryManager.getHashCode(sourcePerson));
        }

        return result;
    }

    private List<GluuSimplePerson> processTargetPersons(List<GluuSimplePerson> targetPersons,
            HashMap<String, Integer> currInumWithEntryHashCodeMap) {
        List<GluuSimplePerson> result = new ArrayList<GluuSimplePerson>();

        for (GluuSimplePerson targetPerson : targetPersons) {
            String personInum = targetPerson.getAttribute(OxTrustConstants.inum);
            if (!currInumWithEntryHashCodeMap.containsKey(personInum)) {
                log.debug("Person with such DN: '{0}' isn't present on source server", targetPerson.getDn());
                result.add(targetPerson);
            }
        }

        return result;
    }

    private HashMap<CacheCompoundKey, GluuInumMap> getPrimaryKeyAttrValueInumMap(List<GluuInumMap> inumMaps) {
        HashMap<CacheCompoundKey, GluuInumMap> result = new HashMap<CacheCompoundKey, GluuInumMap>();

        for (GluuInumMap inumMap : inumMaps) {
            result.put(new CacheCompoundKey(inumMap.getPrimaryKeyValues(), inumMap.getSecondaryKeyValues(),
                    inumMap.getTertiaryKeyValues()), inumMap);
        }

        return result;
    }

    private HashMap<String, GluuInumMap> getInumInumMap(List<GluuInumMap> inumMaps) {
        HashMap<String, GluuInumMap> result = new HashMap<String, GluuInumMap>();

        for (GluuInumMap inumMap : inumMaps) {
            result.put(inumMap.getInum(), inumMap);
        }

        return result;
    }

    private Map<CacheCompoundKey, GluuSimplePerson> getSourcePersonCompoundKeyMap(
            CacheRefreshConfiguration cacheRefreshConfiguration, List<GluuSimplePerson> sourcePersons) {
        Map<CacheCompoundKey, GluuSimplePerson> result = new HashMap<CacheCompoundKey, GluuSimplePerson>();
        Set<CacheCompoundKey> duplicateKeys = new HashSet<CacheCompoundKey>();

        String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration);
        for (GluuSimplePerson sourcePerson : sourcePersons) {
            String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson);
            CacheCompoundKey cacheCompoundKey = new CacheCompoundKey(keyAttributesValues);

            if (result.containsKey(cacheCompoundKey)) {
                duplicateKeys.add(cacheCompoundKey);
            }

            result.put(cacheCompoundKey, sourcePerson);
        }

        for (CacheCompoundKey duplicateKey : duplicateKeys) {
            log.error("Non-deterministic primary key. Skipping user with key: {0}", duplicateKey);
            result.remove(duplicateKey);
        }

        return result;
    }

    private LdapServerConnection[] prepareLdapServerConnections(CacheRefreshConfiguration cacheRefreshConfiguration,
            List<GluuLdapConfiguration> ldapConfigurations) {
        LdapServerConnection[] ldapServerConnections = new LdapServerConnection[ldapConfigurations.size()];
        for (int i = 0; i < ldapConfigurations.size(); i++) {
            ldapServerConnections[i] = prepareLdapServerConnection(cacheRefreshConfiguration,
                    ldapConfigurations.get(i));
            if (ldapServerConnections[i] == null) {
                return null;
            }
        }

        return ldapServerConnections;
    }

    private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration,
            GluuLdapConfiguration ldapConfiguration) {
        String ldapConfig = ldapConfiguration.getConfigId();
        Properties ldapProperties = toLdapProperties(ldapConfiguration);

        LDAPConnectionProvider ldapConnectionProvider = new LDAPConnectionProvider(
                PropertiesDecrypter.decryptProperties(ldapProperties, cryptoConfigurationSalt));

        if (!ldapConnectionProvider.isConnected()) {
            log.error("Failed to connect to LDAP server using configuration {0}", ldapConfig);
            return null;
        }

        return new LdapServerConnection(ldapConfig, ldapConnectionProvider, getBaseDNs(ldapConfiguration));
    }

    private void closeLdapServerConnection(LdapServerConnection... ldapServerConnections) {
        for (LdapServerConnection ldapServerConnection : ldapServerConnections) {
            if ((ldapServerConnection != null) && (ldapServerConnection.getConnectionProvider() != null)) {
                ldapServerConnection.getConnectionProvider().closeConnectionPool();
            }
        }
    }

    private String[] createTwoLettersArray() {
        char[] characters = LETTERS_FOR_SEARCH.toCharArray();
        int lettersCount = characters.length;

        String[] result = new String[lettersCount * lettersCount];
        for (int i = 0; i < lettersCount; i++) {
            for (int j = 0; j < lettersCount; j++) {
                result[i * lettersCount + j] = "" + characters[i] + characters[j];
            }
        }

        return result;
    }

    private String[][] getKeyAttributesValues(String[] attrs, GluuSimplePerson person) {
        String[][] result = new String[attrs.length][];
        for (int i = 0; i < attrs.length; i++) {
            result[i] = person.getAttributes(attrs[i]);
        }

        return result;
    }

    private void updateApplianceStatus(GluuAppliance currentAppliance, long lastRun) {
        GluuAppliance appliance = applianceService.getAppliance();

        appliance.setVdsCacheRefreshLastUpdate(toIntString(lastRun / 1000));
        appliance.setVdsCacheRefreshLastUpdateCount(currentAppliance.getVdsCacheRefreshLastUpdateCount());
        appliance.setVdsCacheRefreshProblemCount(currentAppliance.getVdsCacheRefreshProblemCount());

        ApplianceService.instance().updateAppliance(appliance);
    }

    private String toIntString(Number number) {
        return (number == null) ? null : String.valueOf(number.intValue());
    }

    private String getInumCachePath(CacheRefreshConfiguration cacheRefreshConfiguration) {
        return FilenameUtils.concat(cacheRefreshConfiguration.getSnapshotFolder(), "inum_cache.dat");
    }

    private class LdapServerConnection {
        private String sourceServerName;
        private LDAPConnectionProvider connectionProvider;
        private LdapEntryManager ldapEntryManager;
        private String[] baseDns;

        protected LdapServerConnection(String sourceServerName, LDAPConnectionProvider ldapConnectionProvider,
                String[] baseDns) {
            this.sourceServerName = sourceServerName;
            this.connectionProvider = ldapConnectionProvider;
            this.ldapEntryManager = new LdapEntryManager(new OperationsFacade(connectionProvider));
            this.baseDns = baseDns;
        }

        public final String getSourceServerName() {
            return sourceServerName;
        }

        public final LDAPConnectionProvider getConnectionProvider() {
            return connectionProvider;
        }

        public final LdapEntryManager getLdapEntryManager() {
            return ldapEntryManager;
        }

        public final String[] getBaseDns() {
            return baseDns;
        }

    }

    private CacheRefreshUpdateMethod getUpdateMethod(CacheRefreshConfiguration cacheRefreshConfiguration) {
        String updateMethod = cacheRefreshConfiguration.getUpdateMethod();
        if (StringHelper.isEmpty(updateMethod)) {
            return CacheRefreshUpdateMethod.COPY;
        }

        return CacheRefreshUpdateMethod.getByValue(cacheRefreshConfiguration.getUpdateMethod());
    }

    private String[] getSourceAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) {
        return cacheRefreshConfiguration.getSourceAttributes().toArray(new String[0]);
    }

    private String[] getCompoundKeyAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) {
        return cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]);
    }

    private String[] getCompoundKeyObjectClasses(CacheRefreshConfiguration cacheRefreshConfiguration) {
        return cacheRefreshConfiguration.getKeyObjectClasses().toArray(new String[0]);
    }

    private String[] getCompoundKeyAttributesWithoutValues(CacheRefreshConfiguration cacheRefreshConfiguration) {
        String[] result = cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]);
        for (int i = 0; i < result.length; i++) {
            int index = result[i].indexOf('=');
            if (index != -1) {
                result[i] = result[i].substring(0, index);
            }
        }

        return result;
    }

    private Map<String, String> getTargetServerAttributesMapping(
            CacheRefreshConfiguration cacheRefreshConfiguration) {
        Map<String, String> result = new HashMap<String, String>();
        for (CacheRefreshAttributeMapping attributeMapping : cacheRefreshConfiguration.getAttributeMapping()) {
            result.put(attributeMapping.getDestination(), attributeMapping.getSource());
        }

        return result;
    }

    private Properties toLdapProperties(GluuLdapConfiguration ldapConfiguration) {
        Properties ldapProperties = new Properties();
        ldapProperties.put("servers",
                PropertyUtil.simplePropertiesToCommaSeparatedList(ldapConfiguration.getServers()));
        ldapProperties.put("maxconnections", Integer.toString(ldapConfiguration.getMaxConnections()));
        ldapProperties.put("useSSL", Boolean.toString(ldapConfiguration.isUseSSL()));
        ldapProperties.put("bindDN", ldapConfiguration.getBindDN());
        ldapProperties.put("bindPassword", ldapConfiguration.getBindPassword());

        return ldapProperties;
    }

    private String[] getBaseDNs(GluuLdapConfiguration ldapConfiguration) {
        return ldapConfiguration.getBaseDNsStringsList().toArray(new String[0]);
    }

    public static void main(String[] args) {
        String LETTERS_FOR_SEARCH = "abcdefghijklmnopqrstuvwxyz1";
        char[] characters = LETTERS_FOR_SEARCH.toCharArray();
        int lettersCount = characters.length;

        String[] result = new String[lettersCount * lettersCount];
        for (int i = 0; i < lettersCount; i++) {
            for (int j = 0; j < lettersCount; j++) {
                result[i * lettersCount + j] = "" + characters[i] + characters[j];
            }
        }
        System.out.println(result.length);
    }

}