org.gluu.site.ldap.persistence.LdapEntryManager.java Source code

Java tutorial

Introduction

Here is the source code for org.gluu.site.ldap.persistence.LdapEntryManager.java

Source

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

package org.gluu.site.ldap.persistence;

import java.io.Serializable;
import java.text.ParseException;
import java.util.*;

import org.apache.commons.codec.binary.Base64;
import org.gluu.site.ldap.OperationsFacade;
import org.gluu.site.ldap.exception.ConnectionException;
import org.gluu.site.ldap.persistence.AttributeDataModification.AttributeModificationType;
import org.gluu.site.ldap.persistence.annotation.LdapEnum;
import org.gluu.site.ldap.persistence.exception.AuthenticationException;
import org.gluu.site.ldap.persistence.exception.EntryPersistenceException;
import org.gluu.site.ldap.persistence.exception.InvalidArgumentException;
import org.gluu.site.ldap.persistence.exception.MappingException;
import org.gluu.site.ldap.persistence.property.Getter;
import org.gluu.site.ldap.persistence.property.Setter;
import org.gluu.site.ldap.persistence.util.ReflectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xdi.ldap.model.SearchScope;
import org.xdi.ldap.model.SortOrder;
import org.xdi.ldap.model.VirtualListViewResponse;
import org.xdi.util.ArrayHelper;
import org.xdi.util.StringHelper;

import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
import com.unboundid.util.StaticUtils;

/**
 * LDAP Entry Manager
 *
 * @author Yuriy Movchan Date: 10.07.2010
 */
public class LdapEntryManager extends AbstractEntryManager implements Serializable {

    private static final long serialVersionUID = -2544614410981223105L;

    private static final Logger log = LoggerFactory.getLogger(LdapEntryManager.class);

    private static final Class<?>[] GROUP_BY_ALLOWED_DATA_TYPES = { String.class, Date.class, Integer.class,
            LdapEnum.class };
    private static final Class<?>[] SUM_BY_ALLOWED_DATA_TYPES = { int.class, Integer.class, float.class,
            Float.class, double.class, Double.class };
    private static final String[] NO_STRINGS = new String[0];

    private transient OperationsFacade ldapOperationService;
    private transient List<DeleteNotifier> subscribers;

    public LdapEntryManager(OperationsFacade ldapOperationService) {
        this.ldapOperationService = ldapOperationService;
        subscribers = new LinkedList<DeleteNotifier>();
    }

    public boolean destroy() {
        boolean destroyResult = this.ldapOperationService.destroy();

        return destroyResult;
    }

    public OperationsFacade getLdapOperationService() {
        return ldapOperationService;
    }

    public void addDeleteSubscriber(DeleteNotifier subscriber) {
        subscribers.add(subscriber);
    }

    public void removerDeleteSubscriber(DeleteNotifier subscriber) {
        subscribers.remove(subscriber);
    }

    @Override
    protected void persist(String dn, List<AttributeData> attributes) {
        List<Attribute> ldapAttributes = new ArrayList<Attribute>(attributes.size());
        for (AttributeData attribute : attributes) {
            if (ArrayHelper.isNotEmpty(attribute.getValues())
                    && StringHelper.isNotEmpty(attribute.getValues()[0])) {
                ldapAttributes.add(new Attribute(attribute.getName(), attribute.getValues()));
            }
        }

        // Persist entry
        try {
            boolean result = this.ldapOperationService.addEntry(dn, ldapAttributes);
            if (!result) {
                throw new EntryPersistenceException(String.format("Failed to persist entry: %s", dn));
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(String.format("Failed to persist entry: %s", dn), ex);
        }
    }

    @Override
    public void merge(String dn, List<AttributeDataModification> attributeDataModifications) {
        // Update entry
        try {
            List<Modification> modifications = new ArrayList<Modification>(attributeDataModifications.size());
            for (AttributeDataModification attributeDataModification : attributeDataModifications) {
                AttributeData attribute = attributeDataModification.getAttribute();
                AttributeData oldAttribute = attributeDataModification.getOldAttribute();
                Modification modification = null;
                if (AttributeModificationType.ADD.equals(attributeDataModification.getModificationType())) {
                    modification = new Modification(ModificationType.ADD, attribute.getName(),
                            attribute.getValues());
                } else if (AttributeModificationType.REMOVE
                        .equals(attributeDataModification.getModificationType())) {
                    modification = new Modification(ModificationType.DELETE, oldAttribute.getName(),
                            oldAttribute.getValues());
                } else if (AttributeModificationType.REPLACE
                        .equals(attributeDataModification.getModificationType())) {
                    if (attribute.getValues().length == 1) {
                        modification = new Modification(ModificationType.REPLACE, attribute.getName(),
                                attribute.getValues());
                    } else {
                        String[] oldValues = ArrayHelper.arrayClone(oldAttribute.getValues());
                        String[] newValues = ArrayHelper.arrayClone(attribute.getValues());

                        Arrays.sort(oldValues);
                        Arrays.sort(newValues);

                        boolean[] retainOldValues = new boolean[oldValues.length];
                        Arrays.fill(retainOldValues, false);

                        List<String> addValues = new ArrayList<String>();
                        List<String> removeValues = new ArrayList<String>();

                        // Add new values
                        for (String value : newValues) {
                            int idx = Arrays.binarySearch(oldValues, value, new Comparator<String>() {
                                @Override
                                public int compare(String o1, String o2) {
                                    return o1.toLowerCase().compareTo(o2.toLowerCase());
                                }
                            });
                            if (idx >= 0) {
                                // Old values array contains new value. Retain
                                // old value
                                retainOldValues[idx] = true;
                            } else {
                                // This is new value
                                addValues.add(value);
                            }
                        }

                        // Remove values which we don't have in new values
                        for (int i = 0; i < oldValues.length; i++) {
                            if (!retainOldValues[i]) {
                                removeValues.add(oldValues[i]);
                            }
                        }

                        if (removeValues.size() > 0) {
                            Modification removeModification = new Modification(ModificationType.DELETE,
                                    attribute.getName(), removeValues.toArray(new String[removeValues.size()]));
                            modifications.add(removeModification);
                        }

                        if (addValues.size() > 0) {
                            Modification addModification = new Modification(ModificationType.ADD,
                                    attribute.getName(), addValues.toArray(new String[addValues.size()]));
                            modifications.add(addModification);
                        }
                    }
                }

                if (modification != null) {
                    modifications.add(modification);
                }
            }

            if (modifications.size() > 0) {
                boolean result = this.ldapOperationService.updateEntry(dn, modifications);
                if (!result) {
                    throw new EntryPersistenceException(String.format("Failed to update entry: %s", dn));
                }
            }
        } catch (LDAPException e) {
            throw new EntryPersistenceException(String.format("Failed to update entry: %s", dn), e);
        } catch (Exception ex) {
            throw new EntryPersistenceException(String.format("Failed to update entry: %s", dn), ex);
        }
    }

    @Override
    protected void remove(String dn) {
        // Remove entry
        try {

            for (DeleteNotifier subscriber : subscribers) {
                subscriber.onBeforeRemove(dn);
            }
            this.ldapOperationService.delete(dn);
            for (DeleteNotifier subscriber : subscribers) {
                subscriber.onAfterRemove(dn);
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(String.format("Failed to remove entry: %s", dn), ex);
        }
    }

    @Override
    public void removeWithSubtree(String dn) {
        try {
            for (DeleteNotifier subscriber : subscribers) {
                subscriber.onBeforeRemove(dn);
            }
            this.ldapOperationService.deleteWithSubtree(dn);
            for (DeleteNotifier subscriber : subscribers) {
                subscriber.onAfterRemove(dn);
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(String.format("Failed to remove entry: %s", dn), ex);
        }
    }

    @Override
    protected List<AttributeData> find(String dn, String... ldapReturnAttributes) {
        // Load entry

        try {
            SearchResultEntry entry = this.ldapOperationService.lookup(dn, ldapReturnAttributes);
            List<AttributeData> result = getAttributeDataList(entry);
            if (result != null) {
                return result;
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(String.format("Failed to find entry: %s", dn), ex);
        }

        throw new EntryPersistenceException(String.format("Failed to find entry: %s", dn));
    }

    public <T> List<T> findEntries(Object entry) {
        return findEntries(entry, 0);
    }

    public <T> List<T> findEntries(Object entry, int searchLimit) {
        return findEntries(entry, searchLimit, 0);
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> findEntries(Object entry, int searchLimit, int sizeLimit) {
        if (entry == null) {
            throw new MappingException("Entry to find is null");
        }

        // Check entry class
        Class<T> entryClass = (Class<T>) entry.getClass();
        checkEntryClass(entryClass, false);
        List<PropertyAnnotation> propertiesAnnotations = getEntryPropertyAnnotations(entryClass);

        Object dnValue = getDNValue(entry, entryClass);

        List<AttributeData> attributes = getAttributesListForPersist(entry, propertiesAnnotations);
        Filter searchFilter = createFilterByEntry(entry, entryClass, attributes);

        return findEntries(dnValue.toString(), entryClass, searchFilter, null, searchLimit);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter) {
        return findEntries(baseDN, entryClass, filter, SearchScope.SUB);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, SearchScope scope) {
        return findEntries(baseDN, entryClass, filter, scope, null, 0);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, String[] ldapReturnAttributes,
            Filter filter) {
        return findEntries(baseDN, entryClass, ldapReturnAttributes, filter, SearchScope.SUB);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, String[] ldapReturnAttributes, Filter filter,
            SearchScope scope) {
        return findEntries(baseDN, entryClass, filter, scope, ldapReturnAttributes, 0);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, int searchLimit) {
        return findEntries(baseDN, entryClass, filter, null, searchLimit, 0);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, int searchLimit,
            int sizeLimit) {
        return findEntries(baseDN, entryClass, filter, null, searchLimit, sizeLimit);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, String[] ldapReturnAttributes,
            int searchLimit) {
        return findEntries(baseDN, entryClass, filter, ldapReturnAttributes, searchLimit, 0);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, SearchScope scope,
            String[] ldapReturnAttributes, int searchLimit) {
        return findEntries(baseDN, entryClass, filter, scope, ldapReturnAttributes, searchLimit, 0);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, String[] ldapReturnAttributes,
            int searchLimit, int sizeLimit) {
        return findEntries(baseDN, entryClass, filter, SearchScope.SUB, ldapReturnAttributes, searchLimit,
                sizeLimit);
    }

    public <T> List<T> findEntries(String baseDN, Class<T> entryClass, Filter filter, SearchScope scope,
            String[] ldapReturnAttributes, int searchLimit, int sizeLimit) {
        if (StringHelper.isEmptyString(baseDN)) {
            throw new MappingException("Base DN to find entries is null");
        }

        // Check entry class
        checkEntryClass(entryClass, false);
        String[] objectClasses = getTypeObjectClasses(entryClass);
        List<PropertyAnnotation> propertiesAnnotations = getEntryPropertyAnnotations(entryClass);
        String[] currentLdapReturnAttributes = ldapReturnAttributes;
        if (ArrayHelper.isEmpty(currentLdapReturnAttributes)) {
            currentLdapReturnAttributes = getLdapAttributes(null, propertiesAnnotations, false);
        }

        // Find entries
        Filter searchFilter;
        if (objectClasses.length > 0) {
            searchFilter = addObjectClassFilter(filter, objectClasses);
        } else {
            searchFilter = filter;
        }
        SearchResult searchResult = null;
        try {
            searchResult = this.ldapOperationService.search(baseDN, searchFilter, scope, searchLimit, sizeLimit,
                    null, currentLdapReturnAttributes);

            if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                throw new EntryPersistenceException(
                        String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter));
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(
                    String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex);
        }

        if (searchResult.getEntryCount() == 0) {
            return new ArrayList<T>(0);
        }

        List<T> entries = createEntities(entryClass, propertiesAnnotations, searchResult.getSearchEntries()
                .toArray(new SearchResultEntry[searchResult.getSearchEntries().size()]));

        // Default sort if needed
        sortEntriesIfNeeded(entryClass, entries);

        return entries;
    }

    public <T> List<T> findEntriesVirtualListView(String baseDN, Class<T> entryClass, Filter filter, int startIndex,
            int count, String sortBy, SortOrder sortOrder, VirtualListViewResponse vlvResponse,
            String[] ldapReturnAttributes) {

        if (StringHelper.isEmptyString(baseDN)) {
            throw new MappingException("Base DN to find entries is null");
        }

        // Check entry class
        checkEntryClass(entryClass, false);
        String[] objectClasses = getTypeObjectClasses(entryClass);
        List<PropertyAnnotation> propertiesAnnotations = getEntryPropertyAnnotations(entryClass);
        String[] currentLdapReturnAttributes = ldapReturnAttributes;
        if (ArrayHelper.isEmpty(currentLdapReturnAttributes)) {
            currentLdapReturnAttributes = getLdapAttributes(null, propertiesAnnotations, false);
        }

        // Find entries
        Filter searchFilter;
        if (objectClasses.length > 0) {
            searchFilter = addObjectClassFilter(filter, objectClasses);
        } else {
            searchFilter = filter;
        }

        SearchResult searchResult = null;
        try {

            searchResult = this.ldapOperationService.searchVirtualListView(baseDN, searchFilter, SearchScope.SUB,
                    startIndex, count, sortBy, sortOrder, vlvResponse, currentLdapReturnAttributes);

            if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                throw new EntryPersistenceException(
                        String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter));
            }

        } catch (Exception ex) {
            throw new EntryPersistenceException(
                    String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex);
        }

        if (searchResult.getEntryCount() == 0) {
            return new ArrayList<T>(0);
        }

        List<T> entries = createEntitiesVirtualListView(entryClass, propertiesAnnotations, searchResult
                .getSearchEntries().toArray(new SearchResultEntry[searchResult.getSearchEntries().size()]));

        return entries;
    }

    public <T> boolean contains(String baseDN, Class<T> entryClass, Filter filter) {
        // Check entry class
        checkEntryClass(entryClass, false);
        String[] objectClasses = getTypeObjectClasses(entryClass);
        List<PropertyAnnotation> propertiesAnnotations = getEntryPropertyAnnotations(entryClass);
        String[] ldapReturnAttributes = getLdapAttributes(null, propertiesAnnotations, false);

        return contains(baseDN, filter, objectClasses, ldapReturnAttributes);
    }

    protected boolean contains(String baseDN, List<AttributeData> attributes, String[] objectClasses,
            String... ldapReturnAttributes) {
        Filter[] attributesFilters = createAttributesFilter(attributes);
        Filter attributesFilter = null;
        if (attributesFilters != null) {
            attributesFilter = Filter.createANDFilter(attributesFilters);
        }

        return contains(baseDN, attributesFilter, objectClasses, ldapReturnAttributes);
    }

    protected boolean contains(String baseDN, Filter filter, String[] objectClasses,
            String[] ldapReturnAttributes) {
        if (StringHelper.isEmptyString(baseDN)) {
            throw new MappingException("Base DN to check contain entries is null");
        }

        // Create filter
        Filter searchFilter;
        if (objectClasses.length > 0) {
            searchFilter = addObjectClassFilter(filter, objectClasses);
        } else {
            searchFilter = filter;
        }

        SearchResult searchResult = null;
        try {
            searchResult = this.ldapOperationService.search(baseDN, searchFilter, 1, 1, null, ldapReturnAttributes);
            if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                throw new EntryPersistenceException(
                        String.format("Failed to find entry with baseDN: %s, filter: %s", baseDN, searchFilter));
            }
        } catch (LDAPSearchException ex) {
            if (!ResultCode.NO_SUCH_OBJECT.equals(ex.getResultCode())) {
                throw new EntryPersistenceException(
                        String.format("Failed to find entry with baseDN: %s, filter: %s", baseDN, searchFilter),
                        ex);
            }
        }

        return (searchResult != null) && (searchResult.getEntryCount() > 0);
    }

    private Filter addObjectClassFilter(Filter filter, String[] objectClasses) {
        if (objectClasses.length == 0) {
            return filter;
        }

        Filter[] objectClassFilter = new Filter[objectClasses.length];
        for (int i = 0; i < objectClasses.length; i++) {
            objectClassFilter[i] = Filter.createEqualityFilter(OBJECT_CLASS, objectClasses[i]);
        }
        Filter searchFilter = Filter.createANDFilter(objectClassFilter);
        if (filter != null) {
            searchFilter = Filter.createANDFilter(Filter.createANDFilter(objectClassFilter), filter);
        }
        return searchFilter;
    }

    private <T> List<T> createEntities(Class<T> entryClass, List<PropertyAnnotation> propertiesAnnotations,
            SearchResultEntry... searchResultEntries) {
        List<T> result = new ArrayList<T>(searchResultEntries.length);
        Map<String, List<AttributeData>> entriesAttributes = new HashMap<String, List<AttributeData>>(100);

        int count = 0;
        for (int i = 0; i < searchResultEntries.length; i++) {
            count++;
            SearchResultEntry entry = searchResultEntries[i];
            entriesAttributes.put(entry.getDN(), getAttributeDataList(entry));

            // Remove reference to allow java clean up object
            searchResultEntries[i] = null;

            // Allow java to clean up temporary objects 
            if (count >= 100) {
                List<T> currentResult = createEntities(entryClass, propertiesAnnotations, entriesAttributes);
                result.addAll(currentResult);

                entriesAttributes = new HashMap<String, List<AttributeData>>(100);
                count = 0;
            }
        }

        List<T> currentResult = createEntities(entryClass, propertiesAnnotations, entriesAttributes);
        result.addAll(currentResult);

        return result;
    }

    private <T> List<T> createEntitiesVirtualListView(Class<T> entryClass,
            List<PropertyAnnotation> propertiesAnnotations, SearchResultEntry... searchResultEntries) {

        List<T> result = new LinkedList<T>();
        Map<String, List<AttributeData>> entriesAttributes = new LinkedHashMap<String, List<AttributeData>>(100);

        int count = 0;
        for (int i = 0; i < searchResultEntries.length; i++) {

            count++;

            SearchResultEntry entry = searchResultEntries[i];

            LinkedList<AttributeData> attributeDataLinkedList = new LinkedList<AttributeData>();
            attributeDataLinkedList.addAll(getAttributeDataList(entry));
            entriesAttributes.put(entry.getDN(), attributeDataLinkedList);

            // Remove reference to allow java clean up object
            searchResultEntries[i] = null;

            // Allow java to clean up temporary objects
            if (count >= 100) {

                List<T> currentResult = new LinkedList<T>();
                currentResult.addAll(createEntities(entryClass, propertiesAnnotations, entriesAttributes, false));
                result.addAll(currentResult);

                entriesAttributes = new LinkedHashMap<String, List<AttributeData>>(100);
                count = 0;
            }
        }

        List<T> currentResult = createEntities(entryClass, propertiesAnnotations, entriesAttributes, false);
        result.addAll(currentResult);

        return result;
    }

    private Filter[] createAttributesFilter(List<AttributeData> attributes) {
        if ((attributes == null) || (attributes.size() == 0)) {
            return null;
        }

        List<Filter> results = new ArrayList<Filter>(attributes.size());

        for (AttributeData attribute : attributes) {
            String attributeName = attribute.getName();
            for (String value : attribute.getValues()) {
                Filter filter = Filter.createEqualityFilter(attributeName, value);
                results.add(filter);
            }
        }

        return results.toArray(new Filter[results.size()]);
    }

    private List<AttributeData> getAttributeDataList(SearchResultEntry entry) {
        if (entry == null) {
            return null;
        }

        List<AttributeData> result = new ArrayList<AttributeData>();
        for (Attribute attribute : entry.getAttributes()) {
            String[] attributeValueStrings = NO_STRINGS;
            String attributeName = attribute.getName();
            if (log.isTraceEnabled()) {
                if (attribute.needsBase64Encoding()) {
                    log.trace("Found binary attribute: " + attributeName + ". Is defined in LDAP config: "
                            + ldapOperationService.isBinaryAttribute(attributeName));
                }
            }

            if (attribute.needsBase64Encoding() && ldapOperationService.isBinaryAttribute(attributeName)) {
                byte[][] attributeValues = attribute.getValueByteArrays();
                if (attributeValues != null) {
                    attributeValueStrings = new String[attributeValues.length];
                    for (int i = 0; i < attributeValues.length; i++) {
                        attributeValueStrings[i] = Base64.encodeBase64String(attributeValues[i]);
                        if (log.isTraceEnabled()) {
                            log.trace("Binary attribute: " + attribute.getName() + " value (hex): "
                                    + org.apache.commons.codec.binary.Hex.encodeHexString(attributeValues[i])
                                    + " value (base64): " + attributeValueStrings[i]);
                        }
                    }
                }
            } else {
                attributeValueStrings = attribute.getValues();
            }

            AttributeData tmpAttribute = new AttributeData(attribute.getName(), attributeValueStrings);
            result.add(tmpAttribute);
        }

        return result;
    }

    public boolean authenticate(String userName, String password, String baseDN) {
        try {
            return ldapOperationService.authenticate(userName, password, baseDN);
        } catch (ConnectionException ex) {
            throw new AuthenticationException(String.format("Failed to authenticate user: %s", userName), ex);
        }
    }

    public boolean authenticate(String bindDn, String password) {
        try {
            return ldapOperationService.authenticate(bindDn, password);
        } catch (ConnectionException ex) {
            throw new AuthenticationException(String.format("Failed to authenticate dn: %s", bindDn), ex);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> int countEntries(Object entry) {
        if (entry == null) {
            throw new MappingException("Entry to count is null");
        }

        // Check entry class
        Class<T> entryClass = (Class<T>) entry.getClass();
        checkEntryClass(entryClass, false);
        List<PropertyAnnotation> propertiesAnnotations = getEntryPropertyAnnotations(entryClass);

        Object dnValue = getDNValue(entry, entryClass);

        List<AttributeData> attributes = getAttributesListForPersist(entry, propertiesAnnotations);
        Filter searchFilter = createFilterByEntry(entry, entryClass, attributes);

        return countEntries(dnValue.toString(), entryClass, searchFilter);
    }

    public <T> int countEntries(String baseDN, Class<T> entryClass, Filter filter) {
        if (StringHelper.isEmptyString(baseDN)) {
            throw new MappingException("Base DN to find entries is null");
        }

        // Check entry class
        checkEntryClass(entryClass, false);
        String[] objectClasses = getTypeObjectClasses(entryClass);
        String[] ldapReturnAttributes = new String[] { "" }; // Don't load
        // attributes

        // Find entries
        Filter searchFilter;
        if (objectClasses.length > 0) {
            searchFilter = addObjectClassFilter(filter, objectClasses);
        } else {
            searchFilter = filter;
        }

        int countEntries = 0;
        ASN1OctetString cookie = null;
        SearchResult searchResult = null;
        do {
            Control[] controls = new Control[] { new SimplePagedResultsControl(100, cookie) };
            try {
                searchResult = this.ldapOperationService.search(baseDN, searchFilter, 0, 0, controls,
                        ldapReturnAttributes);
                if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                    throw new EntryPersistenceException(String.format(
                            "Failed to calculate count entries with baseDN: %s, filter: %s", baseDN, searchFilter));
                }
            } catch (Exception ex) {
                throw new EntryPersistenceException(String.format(
                        "Failed to calculate count entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex);
            }

            countEntries += searchResult.getEntryCount();
            // Break infinite loop since cookie isn't empty after reaches end of
            // list
            if ((countEntries == 0) || ((countEntries % 100) != 0)) {
                break;
            }

            cookie = null;
            for (Control control : searchResult.getResponseControls()) {
                if (control instanceof SimplePagedResultsControl) {
                    cookie = ((SimplePagedResultsControl) control).getCookie();
                    break;
                }
            }
        } while (cookie != null);

        return countEntries;
    }

    private <T> Filter createFilterByEntry(Object entry, Class<T> entryClass, List<AttributeData> attributes) {
        Filter[] attributesFilters = createAttributesFilter(attributes);
        Filter attributesFilter = null;
        if (attributesFilters != null) {
            attributesFilter = Filter.createANDFilter(attributesFilters);
        }

        String[] objectClasses = getCustomObjectClasses(entry, entryClass);
        return addObjectClassFilter(attributesFilter, objectClasses);
    }

    public <T> void sortListByProperties(Class<T> entryClass, List<T> entries, boolean caseSensetive,
            String... sortByProperties) {
        // Check input parameters
        if (entries == null) {
            throw new MappingException("Entries list to sort is null");
        }

        if (entries.size() == 0) {
            return;
        }

        if ((sortByProperties == null) || (sortByProperties.length == 0)) {
            throw new InvalidArgumentException(
                    "Invalid list of sortBy properties " + Arrays.toString(sortByProperties));
        }

        // Get getters for all properties
        Getter[][] propertyGetters = new Getter[sortByProperties.length][];
        for (int i = 0; i < sortByProperties.length; i++) {
            String[] tmpProperties = sortByProperties[i].split("\\.");
            propertyGetters[i] = new Getter[tmpProperties.length];
            Class<?> currentEntryClass = entryClass;
            for (int j = 0; j < tmpProperties.length; j++) {
                if (j > 0) {
                    currentEntryClass = propertyGetters[i][j - 1].getReturnType();
                }
                propertyGetters[i][j] = getGetter(currentEntryClass, tmpProperties[j]);
            }

            if (propertyGetters[i][tmpProperties.length - 1] == null) {
                throw new MappingException("Entry should has getteres for all properties " + sortByProperties[i]);
            }

            Class<?> propertyType = propertyGetters[i][tmpProperties.length - 1].getReturnType();
            if (!((propertyType == String.class) || (propertyType == Date.class) || (propertyType == Integer.class)
                    || (propertyType == Integer.TYPE))) {
                throw new MappingException("Entry properties should has String, Date or Integer type. Property: '"
                        + tmpProperties[tmpProperties.length - 1] + "'");
            }
        }

        PropertyComparator<T> comparator = new PropertyComparator<T>(propertyGetters, caseSensetive);
        Collections.sort(entries, comparator);
    }

    public <T> void sortListByProperties(Class<T> entryClass, List<T> entries, String... sortByProperties) {
        sortListByProperties(entryClass, entries, false, sortByProperties);
    }

    private static final class PropertyComparator<T> implements Comparator<T>, Serializable {

        private static final long serialVersionUID = 574848841116711467L;
        private Getter[][] propertyGetters;
        private boolean caseSensetive;

        private PropertyComparator(Getter[][] propertyGetters, boolean caseSensetive) {
            this.propertyGetters = propertyGetters;
            this.caseSensetive = caseSensetive;
        }

        public int compare(T entry1, T entry2) {
            if ((entry1 == null) && (entry2 == null)) {
                return 0;
            }
            if ((entry1 == null) && (entry2 != null)) {
                return -1;
            } else if ((entry1 != null) && (entry2 == null)) {
                return 1;
            }

            int result = 0;
            for (Getter[] curPropertyGetters : propertyGetters) {
                result = compare(entry1, entry2, curPropertyGetters);
                if (result != 0) {
                    break;
                }
            }

            return result;
        }

        public int compare(T entry1, T entry2, Getter[] propertyGetters) {
            Object value1 = ReflectHelper.getPropertyValue(entry1, propertyGetters);
            Object value2 = ReflectHelper.getPropertyValue(entry2, propertyGetters);

            if ((value1 == null) && (value2 == null)) {
                return 0;
            }
            if ((value1 == null) && (value2 != null)) {
                return -1;
            } else if ((value1 != null) && (value2 == null)) {
                return 1;
            }

            if (value1 instanceof Date) {
                return ((Date) value1).compareTo((Date) value2);
            } else if (value1 instanceof Integer) {
                return ((Integer) value1).compareTo((Integer) value2);
            } else if (value1 instanceof String && value2 instanceof String) {
                if (caseSensetive) {
                    return ((String) value1).compareTo((String) value2);
                } else {
                    return ((String) value1).toLowerCase().compareTo(((String) value2).toLowerCase());
                }
            }
            return 0;
        }
    }

    public <T> Map<T, List<T>> groupListByProperties(Class<T> entryClass, List<T> entries, boolean caseSensetive,
            String groupByProperties, String sumByProperties) {
        // Check input parameters
        if (entries == null) {
            throw new MappingException("Entries list to group is null");
        }

        if (entries.size() == 0) {
            return new HashMap<T, List<T>>(0);
        }

        if (StringHelper.isEmpty(groupByProperties)) {
            throw new InvalidArgumentException("List of groupBy properties is null");
        }

        // Get getters for all properties
        Getter[] groupPropertyGetters = getEntryPropertyGetters(entryClass, groupByProperties,
                GROUP_BY_ALLOWED_DATA_TYPES);
        Setter[] groupPropertySetters = getEntryPropertySetters(entryClass, groupByProperties,
                GROUP_BY_ALLOWED_DATA_TYPES);
        Getter[] sumPropertyGetters = getEntryPropertyGetters(entryClass, sumByProperties,
                SUM_BY_ALLOWED_DATA_TYPES);
        Setter[] sumPropertySetter = getEntryPropertySetters(entryClass, sumByProperties,
                SUM_BY_ALLOWED_DATA_TYPES);

        return groupListByPropertiesImpl(entryClass, entries, caseSensetive, groupPropertyGetters,
                groupPropertySetters, sumPropertyGetters, sumPropertySetter);
    }

    private <T> Getter[] getEntryPropertyGetters(Class<T> entryClass, String properties, Class<?>[] allowedTypes) {
        if (StringHelper.isEmpty(properties)) {
            return null;
        }

        String[] tmpProperties = properties.split("\\,");
        Getter[] propertyGetters = new Getter[tmpProperties.length];
        for (int i = 0; i < tmpProperties.length; i++) {
            propertyGetters[i] = getGetter(entryClass, tmpProperties[i].trim());

            if (propertyGetters[i] == null) {
                throw new MappingException("Entry should has getter for property " + tmpProperties[i]);
            }

            Class<?> returnType = propertyGetters[i].getReturnType();
            boolean found = false;
            for (Class<?> clazz : allowedTypes) {
                if (ReflectHelper.assignableFrom(returnType, clazz)) {
                    found = true;
                    break;
                }
            }

            if (!found) {
                throw new MappingException(
                        "Entry property getter should has next data types " + Arrays.toString(allowedTypes));
            }
        }

        return propertyGetters;
    }

    private <T> Setter[] getEntryPropertySetters(Class<T> entryClass, String properties, Class<?>[] allowedTypes) {
        if (StringHelper.isEmpty(properties)) {
            return null;
        }

        String[] tmpProperties = properties.split("\\,");
        Setter[] propertySetters = new Setter[tmpProperties.length];
        for (int i = 0; i < tmpProperties.length; i++) {
            propertySetters[i] = getSetter(entryClass, tmpProperties[i].trim());

            if (propertySetters[i] == null) {
                throw new MappingException("Entry should has setter for property " + tmpProperties[i]);
            }

            Class<?> paramType = ReflectHelper.getSetterType(propertySetters[i]);
            boolean found = false;
            for (Class<?> clazz : allowedTypes) {
                if (ReflectHelper.assignableFrom(paramType, clazz)) {
                    found = true;
                    break;
                }
            }

            if (!found) {
                throw new MappingException(
                        "Entry property setter should has next data types " + Arrays.toString(allowedTypes));
            }
        }

        return propertySetters;
    }

    public <T> Map<T, List<T>> groupListByProperties(Class<T> entryClass, List<T> entries, String groupByProperties,
            String sumByProperties) {
        return groupListByProperties(entryClass, entries, false, groupByProperties, sumByProperties);
    }

    private <T> Map<T, List<T>> groupListByPropertiesImpl(Class<T> entryClass, List<T> entries,
            boolean caseSensetive, Getter[] groupPropertyGetters, Setter[] groupPropertySetters,
            Getter[] sumProperyGetters, Setter[] sumPropertySetter) {
        Map<String, T> keys = new HashMap<String, T>();
        Map<T, List<T>> groups = new IdentityHashMap<T, List<T>>();

        for (T entry : entries) {
            String key = getEntryKey(entry, caseSensetive, groupPropertyGetters);

            T entryKey = keys.get(key);
            if (entryKey == null) {
                try {
                    entryKey = ReflectHelper.createObjectByDefaultConstructor(entryClass);
                } catch (Exception ex) {
                    throw new MappingException(String.format("Entry %s should has default constructor", entryClass),
                            ex);
                }
                try {
                    ReflectHelper.copyObjectPropertyValues(entry, entryKey, groupPropertyGetters,
                            groupPropertySetters);
                } catch (Exception ex) {
                    throw new MappingException("Failed to set values in group Entry", ex);
                }
                keys.put(key, entryKey);
            }

            List<T> groupValues = groups.get(entryKey);
            if (groupValues == null) {
                groupValues = new ArrayList<T>();
                groups.put(entryKey, groupValues);
            }

            try {
                if (sumProperyGetters != null) {
                    ReflectHelper.sumObjectPropertyValues(entryKey, entry, sumProperyGetters, sumPropertySetter);
                }
            } catch (Exception ex) {
                throw new MappingException("Failed to sum values in group Entry", ex);
            }

            groupValues.add(entry);
        }

        return groups;
    }

    public String encodeGeneralizedTime(Date date) {
        if (date == null) {
            return null;
        }

        return StaticUtils.encodeGeneralizedTime(date);
    }

    public Date decodeGeneralizedTime(String date) {
        if (date == null) {
            return null;
        }

        try {
            return StaticUtils.decodeGeneralizedTime(date);
        } catch (ParseException ex) {
            log.error("Failed to parse generalized time {}", date, ex);
        }

        return null;
    }

    public boolean loadLdifFileContent(String ldifFileContent) {
        LDAPConnection connection = null;
        try {
            connection = ldapOperationService.getConnection();
            ResultCode result = LdifDataUtility.instance().importLdifFileContent(connection, ldifFileContent);
            return ResultCode.SUCCESS.equals(result);
        } catch (Exception ex) {
            log.error("Failed to load ldif file", ex);
            return false;
        } finally {
            if (connection != null) {
                ldapOperationService.releaseConnection(connection);
            }
        }
    }

    public String[] getLDIF(String dn) {
        String[] ldif = null;
        try {
            ldif = this.ldapOperationService.lookup(dn).toLDIF();
        } catch (ConnectionException e) {
            log.error("Failed get ldif from " + dn, e);
        }
        ;
        return ldif;
    }

    public List<String[]> getLDIF(String dn, String[] attributes) {
        SearchResult searchResult;
        try {
            searchResult = this.ldapOperationService.search(dn, Filter.create("objectclass=*"), SearchScope.BASE,
                    -1, 0, null, attributes);
            if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s", dn));
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(
                    String.format("Failed to find entries with baseDN: %s, filter: %s", dn, null), ex);
        }

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

        if (searchResult.getEntryCount() == 0) {
            return result;
        }

        for (SearchResultEntry searchResultEntry : searchResult.getSearchEntries()) {
            result.add(searchResultEntry.toLDIF());
        }

        return result;
    }

    public List<String[]> getLDIFTree(String baseDN, Filter searchFilter, String... attributes) {
        SearchResult searchResult;
        try {
            searchResult = this.ldapOperationService.search(baseDN, searchFilter, -1, 0, null, attributes);
            if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) {
                throw new EntryPersistenceException(
                        String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter));
            }
        } catch (Exception ex) {
            throw new EntryPersistenceException(
                    String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex);
        }

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

        if (searchResult.getEntryCount() == 0) {
            return result;
        }

        for (SearchResultEntry searchResultEntry : searchResult.getSearchEntries()) {
            result.add(searchResultEntry.toLDIF());
        }

        return result;
    }

    public int getSupportedLDAPVersion() {
        return this.ldapOperationService.getSupportedLDAPVersion();
    }

}