All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.picketlink.idm.ldap.internal.LDAPIdentityStore Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 *
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.picketlink.idm.ldap.internal;

import org.picketlink.common.constants.LDAPConstants;
import org.picketlink.common.properties.Property;
import org.picketlink.common.properties.query.NamedPropertyCriteria;
import org.picketlink.common.properties.query.PropertyQueries;
import org.picketlink.common.properties.query.TypedPropertyCriteria;
import org.picketlink.idm.IdentityManagementException;
import org.picketlink.idm.config.IdentityStoreConfiguration;
import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
import org.picketlink.idm.config.LDAPMappingConfiguration;
import org.picketlink.idm.credential.handler.annotations.CredentialHandlers;
import org.picketlink.idm.credential.storage.CredentialStorage;
import org.picketlink.idm.internal.AbstractIdentityStore;
import org.picketlink.idm.model.Account;
import org.picketlink.idm.model.AttributedType;
import org.picketlink.idm.model.IdentityType;
import org.picketlink.idm.model.Relationship;
import org.picketlink.idm.query.AttributeParameter;
import org.picketlink.idm.query.Condition;
import org.picketlink.idm.query.IdentityQuery;
import org.picketlink.idm.query.QueryParameter;
import org.picketlink.idm.query.RelationshipQuery;
import org.picketlink.idm.query.RelationshipQueryParameter;
import org.picketlink.idm.query.internal.BetweenCondition;
import org.picketlink.idm.query.internal.EqualCondition;
import org.picketlink.idm.query.internal.GreaterThanCondition;
import org.picketlink.idm.query.internal.InCondition;
import org.picketlink.idm.query.internal.LessThanCondition;
import org.picketlink.idm.query.internal.LikeCondition;
import org.picketlink.idm.spi.CredentialStore;
import org.picketlink.idm.spi.IdentityContext;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.SearchResult;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import static java.util.Map.Entry;
import static org.picketlink.common.constants.LDAPConstants.COMMA;
import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
import static org.picketlink.common.constants.LDAPConstants.EQUAL;
import static org.picketlink.common.constants.LDAPConstants.GROUP_OF_ENTRIES;
import static org.picketlink.common.constants.LDAPConstants.GROUP_OF_NAMES;
import static org.picketlink.common.constants.LDAPConstants.MEMBER;
import static org.picketlink.common.constants.LDAPConstants.OBJECT_CLASS;
import static org.picketlink.common.properties.query.TypedPropertyCriteria.MatchOption;
import static org.picketlink.common.reflection.Reflections.newInstance;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
import static org.picketlink.idm.IDMInternalLog.LDAP_STORE_LOGGER;
import static org.picketlink.idm.IDMInternalMessages.MESSAGES;
import static org.picketlink.idm.ldap.internal.LDAPUtil.formatDate;
import static org.picketlink.idm.ldap.internal.LDAPUtil.parseDate;

/**
 * An IdentityStore implementation backed by an LDAP directory
 *
 * @author Shane Bryzak
 * @author Anil Saldhana
 * @author Pedro Silva
 */
@CredentialHandlers({LDAPPlainTextPasswordCredentialHandler.class})
public class LDAPIdentityStore extends AbstractIdentityStore
        implements CredentialStore {

    public static final String EMPTY_ATTRIBUTE_VALUE = " ";
    public static final String ENTRY_DN_ATTRIBUTE_NAME = "org.picketlink.idm.ldap.entry.dn";

    private LDAPOperationManager operationManager;

    @Override
    public void setup(LDAPIdentityStoreConfiguration config) {
        super.setup(config);

        if (config.isActiveDirectory()) {
            LDAP_STORE_LOGGER.ldapActiveDirectoryConfiguration();
        }

        try {
            this.operationManager = new LDAPOperationManager(getConfig());
        } catch (NamingException e) {
            throw MESSAGES.storeLdapCouldNotCreateContext(e);
        }
    }

    @Override
    public void addAttributedType(IdentityContext context, AttributedType attributedType) {
        if (Relationship.class.isInstance(attributedType)) {
            addRelationship((Relationship) attributedType);
        } else {
            this.operationManager.createSubContext(getBindingDN(attributedType, true), extractAttributes(attributedType, true));

            addToParentAsMember(attributedType);

            attributedType.setId(getEntryIdentifier(attributedType));
        }
    }

    @Override
    public void updateAttributedType(IdentityContext context, AttributedType attributedType) {
        // this store does not support updation of relationship types
        if (Relationship.class.isInstance(attributedType)) {
            LDAP_STORE_LOGGER.ldapRelationshipUpdateNotSupported(attributedType);
        } else {
            BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
            NamingEnumeration attributes = updatedAttributes.getAll();

            this.operationManager.modifyAttributes(getBindingDN(attributedType, true), attributes);
        }
    }

    @Override
    public void removeAttributedType(IdentityContext context, AttributedType attributedType) {
        if (Relationship.class.isInstance(attributedType)) {
            removeRelationship((Relationship) attributedType);
        } else {
            LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());

            this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
        }
    }

    @Override
    protected void removeFromRelationships(final IdentityContext context, final IdentityType identityType) {
        String bindingDN = getBindingDN(identityType, true);

        for (LDAPMappingConfiguration relationshipConfig : getConfig().getRelationshipConfigs()) {
            for (String attributeName : relationshipConfig.getMappedProperties().values()) {
                StringBuilder filter = new StringBuilder();

                filter.append("(&(").append(attributeName).append(EQUAL).append("").append(bindingDN).append("))");

                try {
                    List search = this.operationManager.search(getMappingConfig(relationshipConfig.getRelatedAttributedType()).getBaseDN(), filter.toString(), getMappingConfig(relationshipConfig.getRelatedAttributedType()));

                    for (SearchResult result : search) {
                        Attributes attributes = result.getAttributes();
                        Attribute relationshipAttribute = attributes.get(attributeName);

                        if (relationshipAttribute != null && relationshipAttribute.contains(bindingDN)) {
                            relationshipAttribute.remove(bindingDN);

                            if (relationshipAttribute.size() == 0) {
                                relationshipAttribute.add(EMPTY_ATTRIBUTE_VALUE);
                            }

                            this.operationManager.modifyAttribute(result.getNameInNamespace(), relationshipAttribute);
                        }
                    }
                } catch (NamingException e) {
                    throw new IdentityManagementException("Could not remove " + identityType + " from relationship " + relationshipConfig.getMappedClass(), e);
                }
            }
        }
    }

    @Override
    public  List fetchQueryResults(IdentityContext context, IdentityQuery identityQuery) {
        List results = new ArrayList();

        try {
            if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
                throw new IdentityManagementException("LDAP Identity Store does not support sorted queries.");
            }

            for (Condition condition : identityQuery.getConditions()) {
                if (identityQuery.getConditions().size() == 1 && IdentityType.PARTITION.equals(condition.getParameter())) {
                    // we don't query the ldap tree using only the partition as a parameter due to the cost of doing so.
                    return results;
                }

                if (IdentityType.ID.equals(condition.getParameter())) {
                    if (EqualCondition.class.isInstance(condition)) {
                        EqualCondition equalCondition = (EqualCondition) condition;
                        SearchResult search = this.operationManager
                            .lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);

                        if (search != null) {
                            results.add((V) populateAttributedType(search, null));
                        }
                    }

                    return results;
                }
            }

            if (!IdentityType.class.equals(identityQuery.getIdentityType())) {
                // the ldap store does not support queries based on root types. Except if based on the identifier.
                LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(identityQuery.getIdentityType());
                StringBuilder filter = createIdentityTypeSearchFilter(identityQuery, ldapEntryConfig);
                List search;

                if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
                    search = this.operationManager.searchPaginated(getBaseDN(ldapEntryConfig), filter.toString(), ldapEntryConfig, identityQuery);
                } else {
                    search = this.operationManager.search(getBaseDN(ldapEntryConfig), filter.toString(), ldapEntryConfig);
                }

                for (SearchResult result : search) {
                    results.add((V) populateAttributedType(result, null));
                }

            }
        } catch (Exception e) {
            throw MESSAGES.queryIdentityTypeFailed(identityQuery, e);
        }

        return results;
    }

    @Override
    public  List fetchQueryResults(IdentityContext context, RelationshipQuery query) {
        List results = new ArrayList();

        if (Relationship.class.equals(query.getRelationshipClass())) {
            for (LDAPMappingConfiguration configuration : getConfig().getRelationshipConfigs()) {
                results.addAll(fetchRelationships(query, configuration));
            }
        } else {
            results.addAll(fetchRelationships(query, getMappingConfig(query.getRelationshipClass())));
        }

        return results;
    }

    private String getRelationshipMappedProperty(Class identityType, LDAPMappingConfiguration mappingConfig) {
        final Property property = PropertyQueries.createQuery(mappingConfig.getMappedClass()).addCriteria(new TypedPropertyCriteria(identityType, MatchOption.ALL)).getFirstResult();

        if (property == null) {
            return null;
        }

        return mappingConfig.getMappedProperties().get(property.getName());
    }

    private  List fetchRelationships(final RelationshipQuery query, final LDAPMappingConfiguration mappingConfig) {
        List results = new ArrayList();
        Class relationshipClass = (Class) mappingConfig.getMappedClass();
        Map parameters = query.getParameters();
        LDAPMappingConfiguration relatedTypeConfig = getMappingConfig(mappingConfig.getRelatedAttributedType());
        StringBuilder filter = new StringBuilder();

        filter.append("(&").append(getObjectClassesFilter(relatedTypeConfig));

        List entriesToFilter = new ArrayList();

        for (QueryParameter queryParameter : parameters.keySet()) {
            Object[] values = parameters.get(queryParameter);
            RelationshipQueryParameter relationshipQueryParameter = null;
            String attributeName = null;

            if (RelationshipQueryParameter.class.isInstance(queryParameter)) {
                relationshipQueryParameter = (RelationshipQueryParameter) queryParameter;
                attributeName = mappingConfig.getMappedProperties().get(relationshipQueryParameter.getName());
            } else if (Relationship.IDENTITY.equals(queryParameter)) {
                IdentityType identityType = (IdentityType) values[0];

                if (!mappingConfig.getRelatedAttributedType().isInstance(identityType)) {
                    attributeName = getRelationshipMappedProperty(identityType.getClass(), mappingConfig);
                }
            } else {
                continue;
            }

            for (Object value : values) {
                AttributedType attributedType = (AttributedType) value;

                if (!getConfig().supportsType(attributedType.getClass(), IdentityStoreConfiguration.IdentityOperation.read)) {
                    return results;
                }

                String bindingDN = null;
                SearchResult result = this.operationManager.lookupById(getBaseDN(attributedType), attributedType.getId(), getMappingConfig(attributedType.getClass()));

                if (result != null) {
                    bindingDN = result.getNameInNamespace();

                    if (!attributedType.getClass().equals(relatedTypeConfig.getMappedClass())) {
                        entriesToFilter.add(bindingDN);
                    }
                }

                boolean filterByOwner = attributedType.getClass().equals(relatedTypeConfig.getMappedClass());

                if (attributeName != null) {
                    Property property = PropertyQueries
                            .createQuery(relationshipClass)
                            .addCriteria(new NamedPropertyCriteria(attributeName))
                            .getFirstResult();

                    if (property != null) {
                        filterByOwner = property.getJavaClass().equals(relatedTypeConfig.getMappedClass());
                    }
                }

                if (filterByOwner) {
                    filter.append(this.operationManager.getFilterById(getBaseDN(attributedType), attributedType.getId()));
                } else {
                    filter.append("(").append(attributeName).append(EQUAL).append(bindingDN).append(")");
                }
            }
        }

        filter.append(")");

        try {
            if (filter.length() > 0) {
                String baseDN = getBaseDN(relatedTypeConfig);

                if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                    LDAP_STORE_LOGGER.tracef("Search relationships for type [%s] using filter [%] and baseDN [%s]", relationshipClass, filter.toString(), baseDN);
                }

                List search = this.operationManager.search(baseDN, filter.toString(), relatedTypeConfig);

                for (SearchResult entry : search) {
                    if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                        LDAP_STORE_LOGGER.tracef("Found entry [%s] for relationship ", entry.getNameInNamespace(), relationshipClass);
                    }

                    Attributes ownerAttributes = entry.getAttributes();
                    AttributedType ownerType = populateAttributedType(entry, null);

                    for (Entry memberAttribute : mappingConfig.getMappedProperties().entrySet()) {
                        String attributeName = memberAttribute.getValue();
                        Attribute attribute = ownerAttributes.get(attributeName);

                        if (attribute != null) {
                            NamingEnumeration attributeValues = attribute.getAll();

                            while (attributeValues.hasMore()) {
                                String attributeValue = attributeValues.next().toString();

                                if (!entriesToFilter.isEmpty() && !entriesToFilter.contains(attributeValue)) {
                                    continue;
                                }

                                if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                                    LDAP_STORE_LOGGER
                                        .tracef("Processing relationship [%s] from attribute [%s] with attributeValue [%s]", relationshipClass, attributeName, attributeValue);
                                }

                                if (!isNullOrEmpty(attributeValue.trim())) {
                                    Property associatedProperty = PropertyQueries
                                        .createQuery(relationshipClass)
                                        .addCriteria(new NamedPropertyCriteria(memberAttribute.getKey()))
                                        .getSingleResult();

                                    String memberBaseDN = attributeValue.substring(attributeValue.indexOf(",") + 1);
                                    String dn = attributeValue.substring(0, attributeValue.indexOf(","));

                                    List result = this.operationManager.search(memberBaseDN, dn, null);

                                    if (result.isEmpty()) {
                                        throw new IdentityManagementException("Associated entry does not exists [" + attributeValue + "].");
                                    }

                                    Property property = PropertyQueries
                                        .createQuery(relationshipClass)
                                        .addCriteria(new TypedPropertyCriteria(mappingConfig.getRelatedAttributedType()))
                                        .getSingleResult();

                                    if (property.getJavaClass().isAssignableFrom(ownerType.getClass())) {
                                        V relationship = newInstance(relationshipClass);

                                        property.setValue(relationship, ownerType);

                                        SearchResult member = result.get(0);

                                        AttributedType relType = populateAttributedType(member, null);

                                        if (associatedProperty.getJavaClass().isAssignableFrom(relType.getClass())) {
                                            associatedProperty.setValue(relationship, relType);

                                            if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                                                LDAP_STORE_LOGGER
                                                    .tracef("Relationship [%s] created from attribute [%s] with attributeValue [%s]", relationshipClass, attributeName, attributeValue);
                                            }

                                            results.add(relationship);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw MESSAGES.queryRelationshipFailed(query, e);
        }

        return results;
    }

    @Override
    public void storeCredential(IdentityContext context, Account account, CredentialStorage storage) {
        //no-op. operation no supported by this store
    }

    @Override
    public  T retrieveCurrentCredential(IdentityContext context, Account
            account, Class storageClass) {
        //no-op. operation no supported by this store
        return null;
    }

    @Override
    public  List retrieveCredentials(IdentityContext context, Account
            account, Class storageClass) {
        //no-op. operation no supported by this store
        return null;
    }

    @Override
    public void removeCredential(IdentityContext context, Account account, Class storageClass) {
        // does not makes sense remove credentials in LDAP. You probably want to disable an account ?
        throw MESSAGES.notImplemented();
    }

    @Override
    protected void removeCredentials(final IdentityContext context, final Account account) {
        // not supported
    }

    private String getBaseDN(final LDAPMappingConfiguration ldapEntryConfig) {
        String baseDN = getConfig().getBaseDN();

        if (ldapEntryConfig.getBaseDN() != null) {
            baseDN = ldapEntryConfig.getBaseDN();
        }

        return baseDN;
    }

    protected  StringBuilder createIdentityTypeSearchFilter(final IdentityQuery identityQuery, final LDAPMappingConfiguration ldapEntryConfig) {
        StringBuilder filter = new StringBuilder();

        for (Condition condition : identityQuery.getConditions()) {
            QueryParameter queryParameter = condition.getParameter();

            if (!IdentityType.ID.equals(queryParameter)) {
                if (AttributeParameter.class.isInstance(queryParameter)) {
                    AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
                    String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());

                    if (attributeName != null) {
                        if (EqualCondition.class.isInstance(condition)) {
                            EqualCondition equalCondition = (EqualCondition) condition;
                            Object parameterValue = equalCondition.getValue();

                            if (Date.class.isInstance(parameterValue)) {
                                parameterValue = formatDate((Date) parameterValue);
                            }

                            filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
                        } else if (LikeCondition.class.isInstance(condition)) {
                            LikeCondition likeCondition = (LikeCondition) condition;
                            String parameterValue = (String) likeCondition.getValue();

                        } else if (GreaterThanCondition.class.isInstance(condition)) {
                            GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
                            Comparable parameterValue = (Comparable) greaterThanCondition.getValue();

                            if (Date.class.isInstance(parameterValue)) {
                                parameterValue = formatDate((Date) parameterValue);
                            }

                            if (greaterThanCondition.isOrEqual()) {
                                filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
                            } else {
                                filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
                            }
                        } else if (LessThanCondition.class.isInstance(condition)) {
                            LessThanCondition lessThanCondition = (LessThanCondition) condition;
                            Comparable parameterValue = (Comparable) lessThanCondition.getValue();

                            if (Date.class.isInstance(parameterValue)) {
                                parameterValue = formatDate((Date) parameterValue);
                            }

                            if (lessThanCondition.isOrEqual()) {
                                filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
                            } else {
                                filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
                            }
                        } else if (BetweenCondition.class.isInstance(condition)) {
                            BetweenCondition betweenCondition = (BetweenCondition) condition;
                            Comparable x = betweenCondition.getX();
                            Comparable y = betweenCondition.getY();

                            if (Date.class.isInstance(x)) {
                                x = formatDate((Date) x);
                            }

                            if (Date.class.isInstance(y)) {
                                y = formatDate((Date) y);
                            }

                            filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
                        } else if (InCondition.class.isInstance(condition)) {
                            InCondition inCondition = (InCondition) condition;
                            Object[] valuesToCompare = inCondition.getValue();

                            filter.append("(&(");

                            for (int i = 0; i< valuesToCompare.length; i++) {
                                Object value = valuesToCompare[i];

                                filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
                            }

                            filter.append("))");
                        } else {
                            throw new IdentityManagementException("Unsupported query condition [" + condition + "].");
                        }
                    }
                }
            }
        }


        filter.insert(0, "(&(");

        if (ldapEntryConfig != null) {
            filter.append(getObjectClassesFilter(ldapEntryConfig));
        } else {
            filter.append("(").append(OBJECT_CLASS).append(EQUAL).append("*").append(")");
        }

        filter.append("))");

        return filter;
    }

    private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
        StringBuilder builder = new StringBuilder();

        for (String objectClass : ldapEntryConfig.getObjectClasses()) {
            builder.append("(objectClass=").append(objectClass).append(")");
        }

        return builder;
    }

    private void addRelationship(Relationship relationship) {
        LDAPMappingConfiguration mappingConfig = getMappingConfig(relationship.getClass());
        AttributedType ownerType = getRelationshipOwner(relationship);
        Attributes entryAttributes = this.operationManager.getAttributes(ownerType.getId(), getBaseDN(ownerType), mappingConfig);

        for (String relationshipTypeProperty : mappingConfig.getMappedProperties().keySet()) {
            Property relationshipProperty = PropertyQueries
                    .createQuery(relationship.getClass())
                    .addCriteria(new NamedPropertyCriteria(relationshipTypeProperty))
                    .getSingleResult();

            Attribute attribute = entryAttributes.get(mappingConfig.getMappedProperties().get(relationshipTypeProperty));

            if (attribute != null) {
                List membersToRemove = new ArrayList();
                String memberDN = getBindingDN(relationshipProperty.getValue(relationship), true);

                try {
                    NamingEnumeration attributeValues = attribute.getAll();

                    while (attributeValues.hasMore()) {
                        Object value = attributeValues.next();

                        if (value.toString().trim().equals(EMPTY_ATTRIBUTE_VALUE.trim())) {
                            membersToRemove.add(EMPTY_ATTRIBUTE_VALUE);
                            membersToRemove.add(EMPTY_ATTRIBUTE_VALUE.trim());
                        }

                        if (value.toString().toLowerCase().equals(memberDN.toLowerCase())) {
                            membersToRemove.add(value.toString());
                        }
                    }

                    for (String memberToRemove : membersToRemove) {
                        attribute.remove(memberToRemove);
                    }
                } catch (NamingException ne) {
                    throw new IdentityManagementException("Could not iterate over members for relationship [" + relationship + "].", ne);
                }

                attribute.add(memberDN);

                this.operationManager.modifyAttribute(getBindingDN(ownerType, true), attribute);
            }
        }
    }

    private AttributedType getRelationshipOwner(final Relationship relationship) {
        Class ownertType = getMappingConfig(relationship.getClass()).getRelatedAttributedType();
        Property property = PropertyQueries
                .createQuery(relationship.getClass())
                .addCriteria(new TypedPropertyCriteria(ownertType))
                .getSingleResult();

        return property.getValue(relationship);
    }

    private void removeRelationship(final Relationship relationship) {
        LDAPMappingConfiguration mappingConfig = getMappingConfig(relationship.getClass());
        AttributedType ownerType = getRelationshipOwner(relationship);
        Attributes ownerAttributes = this.operationManager.getAttributes(ownerType.getId(), getBaseDN(ownerType), mappingConfig);

        for (String typeProperty : mappingConfig.getMappedProperties().keySet()) {
            Property relProperty = PropertyQueries
                    .createQuery(relationship.getClass())
                    .addCriteria(new NamedPropertyCriteria(typeProperty))
                    .getSingleResult();

            Attribute mappedAttribute = ownerAttributes.get(mappingConfig.getMappedProperties().get(typeProperty));

            if (mappedAttribute != null) {
                String childDN = getBindingDN(relProperty.getValue(relationship), true);
                NamingEnumeration members = null;

                try {
                    members = mappedAttribute.getAll();

                    while (members.hasMoreElements()) {
                        Object next = members.next();

                        if (next.toString().equalsIgnoreCase(childDN)) {
                            mappedAttribute.remove(next);
                        }
                    }
                } catch (Exception e) {
                    throw new IdentityManagementException("Could not remove relationship [" + relationship + "].", e);
                } finally {
                    if (members != null) {
                        try {
                            members.close();
                        } catch (NamingException ignore) {
                        }
                    }
                }
            }

            if (mappedAttribute != null) {
                if (mappedAttribute.size() == 0) {
                    mappedAttribute.add(EMPTY_ATTRIBUTE_VALUE);
                }

                this.operationManager.modifyAttribute(getBindingDN(ownerType, true), mappedAttribute);
            }
        }
    }

    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
        return populateAttributedType(searchResult, attributedType, 0);
    }

    private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
        try {
            String entryDN = searchResult.getNameInNamespace();
            String entryBaseDN = entryDN.substring(entryDN.indexOf(COMMA) + 1);
            Attributes attributes = searchResult.getAttributes();

            if (attributedType == null) {
                attributedType = newInstance(getConfig().getSupportedTypeByBaseDN(entryBaseDN, getEntryObjectClasses(attributes)));
            }

            attributedType.setAttribute(new org.picketlink.idm.model.Attribute(ENTRY_DN_ATTRIBUTE_NAME, entryDN));

            LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());

            if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
                return null;
            }

            if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                LDAP_STORE_LOGGER.tracef("Populating attributed type [%s] from DN [%s]", attributedType, entryDN);
            }

            NamingEnumeration ldapAttributes = attributes.getAll();

            while (ldapAttributes.hasMore()) {
                Attribute ldapAttribute = ldapAttributes.next();
                Object attributeValue;

                try {
                    attributeValue = ldapAttribute.get();
                } catch (NoSuchElementException nsee) {
                    continue;
                }

                String ldapAttributeName = ldapAttribute.getID();

                if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
                    attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
                } else {
                    String attributeName = findAttributeName(mappingConfig.getMappedProperties(), ldapAttributeName);

                    if (attributeName != null) {
                        // Find if it's java property or attribute
                        Property property = PropertyQueries
                                .createQuery(attributedType.getClass())
                                .addCriteria(new NamedPropertyCriteria(attributeName)).getFirstResult();

                        if (property != null) {
                            if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                                LDAP_STORE_LOGGER.tracef("Populating property [%s] from ldap attribute [%s] with value [%s] from DN [%s].", property.getName(), ldapAttributeName, attributeValue, entryBaseDN);
                            }

                            if (property.getJavaClass().equals(Date.class)) {
                                property.setValue(attributedType, parseDate(attributeValue.toString()));
                            } else {
                                property.setValue(attributedType, attributeValue);
                            }
                        } else {
                            if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                                LDAP_STORE_LOGGER.tracef("Populating attribute [%s] from ldap attribute [%s] with value [%s] from DN [%s].", attributeName, ldapAttributeName, attributeValue, entryBaseDN);
                            }

                            attributedType.setAttribute(new org.picketlink.idm.model.Attribute(attributeName, (Serializable) attributeValue));
                        }
                    }
                }
            }

            if (IdentityType.class.isInstance(attributedType)) {
                IdentityType identityType = (IdentityType) attributedType;

                String createdTimestamp = attributes.get(CREATE_TIMESTAMP).get().toString();

                identityType.setCreatedDate(parseDate(createdTimestamp));
            }

            LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());

            if (mappingConfig.getParentMembershipAttributeName() != null) {
                StringBuilder filter = new StringBuilder("(&");

                filter
                    .append("(")
                        .append(getObjectClassesFilter(entryConfig))
                    .append(")")
                    .append("(")
                        .append(mappingConfig.getParentMembershipAttributeName())
                        .append(EQUAL).append("")
                        .append(getBindingDN(attributedType, false))
                        .append(COMMA)
                    .append(entryBaseDN)
                    .append(")");

                filter.append(")");

                if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                    LDAP_STORE_LOGGER.tracef("Searching parent entry for DN [%s] using filter [%s].", entryDN, filter.toString());
                }

                List search = this.operationManager.search(getConfig().getBaseDN(), filter.toString(), entryConfig);

                if (!search.isEmpty()) {
                    SearchResult next = search.get(0);

                    Property parentProperty = PropertyQueries
                            .createQuery(attributedType.getClass())
                            .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();

                    if (parentProperty != null) {
                        String parentDN = next.getNameInNamespace();
                        String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
                        Class baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));

                        if (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
                            if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                                LDAP_STORE_LOGGER.tracef("Found parent [%s] for entry for DN [%s].", parentDN, entryDN);
                            }

                            int hierarchyDepthCount1 = ++hierarchyDepthCount;

                            parentProperty.setValue(attributedType, populateAttributedType(next, null, hierarchyDepthCount1));
                        }
                    }
                } else {
                    if (LDAP_STORE_LOGGER.isTraceEnabled()) {
                        LDAP_STORE_LOGGER.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
                    }
                }
            }
        } catch (Exception e) {
            throw new IdentityManagementException("Could not populate attribute type " + attributedType + ".", e);
        }

        return attributedType;
    }

    private String findAttributeName(Map attrMapping, String ldapAttributeName) {
        for (Map.Entry currentAttr : attrMapping.entrySet()) {
            if (currentAttr.getValue().equalsIgnoreCase(ldapAttributeName)) {
                return currentAttr.getKey();
            }
        }

        return null;
    }

    private List getEntryObjectClasses(final Attributes attributes) throws NamingException {
        Attribute objectClassesAttribute = attributes.get(OBJECT_CLASS);
        List objectClasses = new ArrayList();

        if (objectClassesAttribute == null) {
            return objectClasses;
        }

        NamingEnumeration all = objectClassesAttribute.getAll();

        while (all.hasMore()) {
            objectClasses.add(all.next().toString());
        }

        return objectClasses;
    }

    protected BasicAttributes extractAttributes(AttributedType attributedType, boolean isCreate) {
        BasicAttributes entryAttributes = new BasicAttributes();
        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
        Map mappedProperties = mappingConfig.getMappedProperties();

        for (String propertyName : mappedProperties.keySet()) {
            if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
                Property property = PropertyQueries
                        .createQuery(attributedType.getClass())
                        .addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();

                Object propertyValue = null;
                if (property != null) {
                    // Mapped Java property on the object
                    propertyValue = property.getValue(attributedType);
                } else {
                    // Not mapped property. So fallback to attribute
                    org.picketlink.idm.model.Attribute attribute = attributedType.getAttribute(propertyName);
                    if (attribute != null) {
                        propertyValue = attribute.getValue();
                    }
                }

                if (AttributedType.class.isInstance(propertyValue)) {
                    AttributedType referencedType = (AttributedType) propertyValue;
                    propertyValue = getBindingDN(referencedType, true);
                } else {
                    if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
                        propertyValue = EMPTY_ATTRIBUTE_VALUE;
                    }
                }

                entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
            }
        }

        // Don't extract object classes for update
        if (isCreate) {
            LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());

            BasicAttribute objectClassAttribute = new BasicAttribute(OBJECT_CLASS);

            for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
                objectClassAttribute.add(objectClassValue);

                if (objectClassValue.equals(GROUP_OF_NAMES)
                        || objectClassValue.equals(GROUP_OF_ENTRIES)
                        || objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
                    entryAttributes.put(MEMBER, EMPTY_ATTRIBUTE_VALUE);
                }
            }

            entryAttributes.put(objectClassAttribute);
        }

        return entryAttributes;
    }

    private LDAPMappingConfiguration getMappingConfig(Class attributedType) {
        LDAPMappingConfiguration mappingConfig = getConfig().getMappingConfig(attributedType);

        if (mappingConfig == null) {
            throw new IdentityManagementException("Not mapped type [" + attributedType + "].");
        }

        return mappingConfig;
    }

    public LDAPOperationManager getOperationManager() {
        return this.operationManager;
    }

    public String getBindingDN(AttributedType attributedType, boolean appendBaseDN) {
        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
        Property idProperty = mappingConfig.getIdProperty();

        String baseDN;

        if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
            baseDN = "";
        } else {
            baseDN = COMMA + getBaseDN(attributedType);
        }

        Property bindingProperty = mappingConfig.getBindingProperty();
        String bindingAttribute;
        String dn;

        if (bindingProperty == null) {
            bindingAttribute = mappingConfig.getMappedProperties().get(idProperty.getName());
            dn = idProperty.getValue(attributedType);
        } else {
            bindingAttribute = mappingConfig.getMappedProperties().get(bindingProperty.getName());
            dn = mappingConfig.getBindingProperty().getValue(attributedType);
        }

        return bindingAttribute + EQUAL + dn + baseDN;
    }

    private String getBaseDN(AttributedType attributedType) {
        LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
        String baseDN = mappingConfig.getBaseDN();
        String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));

        if (parentDN != null) {
            baseDN = parentDN;
        } else {
            Property parentProperty = PropertyQueries
                    .createQuery(attributedType.getClass())
                    .addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();

            if (parentProperty != null) {
                AttributedType parentType = parentProperty.getValue(attributedType);

                if (parentType != null) {
                    Property parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();

                    String parentId = parentIdProperty.getValue(parentType);

                    String parentBaseDN = mappingConfig.getParentMapping().get(parentId);

                    if (parentBaseDN != null) {
                        baseDN = parentBaseDN;
                    } else {
                        baseDN = getBaseDN(parentType);
                    }
                }
            }
        }

        if (baseDN == null) {
            baseDN = getConfig().getBaseDN();
        }

        return baseDN;
    }

    protected void addToParentAsMember(final AttributedType attributedType) {
        LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());

        if (entryConfig.getParentMembershipAttributeName() != null) {
            Property parentProperty = PropertyQueries
                    .createQuery(attributedType.getClass())
                    .addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
                    .getFirstResult();

            if (parentProperty != null) {
                AttributedType parentType = parentProperty.getValue(attributedType);

                if (parentType != null) {
                    Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
                    Attribute attribute = attributes.get(entryConfig.getParentMembershipAttributeName());

                    attribute.add(getBindingDN(attributedType, true));

                    this.operationManager.modifyAttribute(getBindingDN(parentType, true), attribute);
                }
            }
        }
    }

    protected String getEntryIdentifier(final AttributedType attributedType) {
        try {
            // we need this to retrieve the entry's identifier from the ldap server
            List search = this.operationManager.search(getBaseDN(attributedType), "(" + getBindingDN(attributedType, false) + ")", getMappingConfig(attributedType.getClass()));
            Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());

            if (id == null) {
                throw new IdentityManagementException("Could not retrieve identifier for entry [" + getBindingDN(attributedType, true) + "].");
            }

            return this.operationManager.decodeEntryUUID(id.get());
        } catch (NamingException ne) {
            throw new IdentityManagementException("Could not add type [" + attributedType + "].", ne);
        }
    }

}