Package org.jboss.identity.idm.impl.store.ldap

Source Code of org.jboss.identity.idm.impl.store.ldap.LDAPIdentityStoreImpl

/*
* JBoss, a division of Red Hat
* Copyright 2006, Red Hat Middleware, LLC, and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.identity.idm.impl.store.ldap;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.InitialContext;
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.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.SortControl;

import org.jboss.identity.idm.exception.IdentityException;
import org.jboss.identity.idm.impl.NotYetImplementedException;
import org.jboss.identity.idm.impl.api.SimpleAttribute;
import org.jboss.identity.idm.impl.helper.Tools;
import org.jboss.identity.idm.impl.model.ldap.LDAPIdentityObjectImpl;
import org.jboss.identity.idm.impl.model.ldap.LDAPIdentityObjectRelationshipImpl;
import org.jboss.identity.idm.impl.store.FeaturesMetaDataImpl;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityObjectAttributeMetaData;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityObjectTypeMetaData;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityStoreConfigurationMetaData;
import org.jboss.identity.idm.spi.configuration.IdentityStoreConfigurationContext;
import org.jboss.identity.idm.spi.exception.OperationNotSupportedException;
import org.jboss.identity.idm.spi.model.IdentityObject;
import org.jboss.identity.idm.spi.model.IdentityObjectAttribute;
import org.jboss.identity.idm.spi.model.IdentityObjectCredential;
import org.jboss.identity.idm.spi.model.IdentityObjectRelationship;
import org.jboss.identity.idm.spi.model.IdentityObjectRelationshipType;
import org.jboss.identity.idm.spi.model.IdentityObjectType;
import org.jboss.identity.idm.spi.store.FeaturesMetaData;
import org.jboss.identity.idm.spi.store.IdentityStore;
import org.jboss.identity.idm.spi.store.IdentityStoreInvocationContext;
import org.jboss.identity.idm.spi.store.IdentityStoreSession;
import org.jboss.identity.idm.spi.store.IdentityObjectSearchCriteriaType;
import org.jboss.identity.idm.spi.search.IdentityObjectSearchCriteria;

/**
* @author <a href="mailto:boleslaw.dawidowicz at redhat.com">Boleslaw Dawidowicz</a>
* @version : 0.1 $
*/
public class LDAPIdentityStoreImpl implements IdentityStore
{

   //TODO: external JNDI
   //TODO: more options for connection configuration
   //TODO: JNDI connection credentials encoding (pluggable?)

   private static Logger log = Logger.getLogger(LDAPIdentityStoreImpl.class.getName());

   private final String id;

   private FeaturesMetaData supportedFeatures;

   LDAPIdentityStoreConfiguration configuration;

   IdentityStoreConfigurationMetaData configurationMD;

   private static Set<IdentityObjectSearchCriteriaType> supportedSearchConstraintTypes =
      new HashSet<IdentityObjectSearchCriteriaType>();

   // <IdentityObjectType name, <Attribute name, MD>
   private Map<String, Map<String, IdentityObjectAttributeMetaData>> attributesMetaData = new HashMap<String, Map<String, IdentityObjectAttributeMetaData>>();

   static {
      // List all supported controls classes

      //TODO: attribute filter
      supportedSearchConstraintTypes.add(IdentityObjectSearchCriteriaType.SORT);
      supportedSearchConstraintTypes.add(IdentityObjectSearchCriteriaType.PAGE);
      supportedSearchConstraintTypes.add(IdentityObjectSearchCriteriaType.NAME_FILTER);
      //supportedSearchControls.add(AttributeFilterSearchControl.class);
   }

   public LDAPIdentityStoreImpl(String id)
   {
      this.id = id;
   }

   public void bootstrap(IdentityStoreConfigurationContext configurationContext) throws IdentityException
   {
      if (configurationContext == null)
      {
         throw new IllegalArgumentException("Configuration context is null");
      }

      this.configurationMD = configurationContext.getStoreConfigurationMetaData();

      configuration = new SimpleLDAPIdentityStoreConfiguration(configurationMD);

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

      for (IdentityObjectType identityObjectType : configuration.getConfiguredTypes())
      {
         if (!configuration.getTypeConfiguration(identityObjectType.getName()).isAllowCreateEntry())
         {
            readOnlyObjectTypes.add(identityObjectType.getName());
         }
      }

      supportedFeatures = new FeaturesMetaDataImpl(configurationMD, supportedSearchConstraintTypes, false, false, readOnlyObjectTypes);

      // Attribute mappings - helper structures

      for (IdentityObjectTypeMetaData identityObjectTypeMetaData : configurationMD.getSupportedIdentityTypes())
      {
         Map<String, IdentityObjectAttributeMetaData> metadataMap = new HashMap<String, IdentityObjectAttributeMetaData>();
         for (IdentityObjectAttributeMetaData attributeMetaData : identityObjectTypeMetaData.getAttributes())
         {
            metadataMap.put(attributeMetaData.getName(), attributeMetaData);
         }

         attributesMetaData.put(identityObjectTypeMetaData.getName(), metadataMap);

      }
   }

   public IdentityStoreSession createIdentityStoreSession()
   {

      return new LDAPIdentityStoreSessionImpl(
         "com.sun.jndi.ldap.LdapCtxFactory",
         configuration.getProviderURL(),
         "simple",
         configuration.getAdminDN(),
         configuration.getAdminPassword());

   }

   public String getId()
   {
      return id;
   }

   public FeaturesMetaData getSupportedFeatures()
   {
      return supportedFeatures;
   }

   public IdentityObject createIdentityObject(IdentityStoreInvocationContext invocationCtx, String name, IdentityObjectType identityObjectType) throws IdentityException
   {
      return createIdentityObject(invocationCtx, name, identityObjectType, null);
   }

   public IdentityObject createIdentityObject(IdentityStoreInvocationContext invocationCtx,
                                              String name,
                                              IdentityObjectType type,
                                              Map<String, String[]> attributes) throws IdentityException
   {
      if (name == null)
      {
         throw new IdentityException("Name cannot be null");
      }

      checkIOType(type);

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".createIdentityObject with name: " + name + " and type: " + type.getName());
      }

      LdapContext ldapContext = getLDAPContext(invocationCtx);

      try
      {
         //  If there are many contexts specified in the configuration the first one is used
         LdapContext ctx = (LdapContext)ldapContext.lookup(getTypeConfiguration(invocationCtx, type).getCtxDNs()[0]);

         //We store new entry using set of attributes. This should give more flexibility then
         //extending identity object from ContextDir - configure what objectClass place there
         Attributes attrs = new BasicAttributes(true);

         //create attribute using provided configuration
         Map<String, String[]> attributesToAdd = getTypeConfiguration(invocationCtx, type).getCreateEntryAttributeValues();

         //merge
         if (attributes != null)
         {
            for (Map.Entry<String, String[]> entry : attributes.entrySet())
            {

               if (!attributesToAdd.containsKey(entry.getKey()))
               {
                  attributesToAdd.put(entry.getKey(), entry.getValue());
               }
               else
               {
                  List<String> list1 = Arrays.asList(attributesToAdd.get(entry.getKey()));
                  List<String> list2 = Arrays.asList(entry.getValue());

                  list1.addAll(list2);

                  String[] vals = list1.toArray(new String[list1.size()]);

                  attributesToAdd.put(entry.getKey(), vals);

               }
            }
         }

         //attributes
         for (Iterator it1 = attributesToAdd.keySet().iterator(); it1.hasNext();)
         {
            String attributeName = (String)it1.next();


            Attribute attr = new BasicAttribute(attributeName);
            String[] attributeValues = attributesToAdd.get(attributeName);

            //values

            for (String attrValue : attributeValues)
            {
               attr.add(attrValue);
            }

            attrs.put(attr);
         }

         // Make it RFC 2253 compliant
         LdapName validLDAPName = new LdapName(getTypeConfiguration(invocationCtx, type).getIdAttributeName().concat("=").concat(name));

         log.finer("creating ldap entry for: " + validLDAPName + "; " + attrs);
         ctx.createSubcontext(validLDAPName, attrs);
      }
      catch (Exception e)
      {
         throw new IdentityException("Failed to create identity object", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }                                                                 

      return findIdentityObject(invocationCtx, name, type);

   }

   public void removeIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObject identity) throws IdentityException
   {
      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".removeIdentityObject: " + identity);
      }

      LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(invocationCtx, identity);

      String dn = ldapIdentity.getDn();

      if (dn == null)
      {
         throw new IdentityException("Cannot obtain DN of identity");
      }

      LdapContext ldapContext = getLDAPContext(invocationCtx);

      try
      {
         log.finer("removing entry: " + dn);
         ldapContext.unbind(dn);
      }
      catch (Exception e)
      {
         throw new IdentityException("Failed to remove identity: ", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }

   }

   public int getIdentityObjectsCount(IdentityStoreInvocationContext ctx, IdentityObjectType identityType) throws IdentityException
   {
      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".getIdentityObjectsCount for type: " + identityType);
      }

      checkIOType(identityType);

      try
      {
         String filter = getTypeConfiguration(ctx, identityType).getEntrySearchFilter();

         if (filter != null && filter.length() > 0)
         {
            // chars are escaped in filterArgs so we must replace it manually
            filter = filter.replaceAll("\\{0\\}", "*");
         }
         else
         {
            //search all entries
            filter = "(".concat(getTypeConfiguration(ctx, identityType).getIdAttributeName()).concat("=").concat("*").concat(")");
         }


         String[] entryCtxs = getTypeConfiguration(ctx, identityType).getCtxDNs();

         //log.debug("Search filter: " + filter);
         List sr = searchIdentityObjects(ctx,
            entryCtxs,
            filter,
            null,
            new String[]{getTypeConfiguration(ctx, identityType).getIdAttributeName()},
            null);

         return sr.size();

      }
      catch (NoSuchElementException e)
      {
         //log.debug("No identity object found", e);
      }
      catch (Exception e)
      {
         throw new IdentityException("User search failed.", e);
      }
      return 0;
   }

   public IdentityObject findIdentityObject(IdentityStoreInvocationContext invocationCtx, String name, IdentityObjectType type) throws IdentityException
   {

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".findIdentityObject with name: " + name + "; and type: " + type);
      }

      Context ctx = null;
      checkIOType(type);
      try
      {
         //log.debug("name = " + name);

         if (name == null)
         {
            throw new IdentityException("Identity object name canot be null");
         }

         String filter = getTypeConfiguration(invocationCtx, type).getEntrySearchFilter();
         List sr = null;


         String[] entryCtxs = getTypeConfiguration(invocationCtx, type).getCtxDNs();


         if (filter != null && filter.length() > 0)
         {
            Object[] filterArgs = {name};
            sr = searchIdentityObjects(invocationCtx,
               entryCtxs,
               filter,
               filterArgs,
               new String[]{getTypeConfiguration(invocationCtx, type).getIdAttributeName()},
               null);
         }
         else
         {
            //search all entries
            filter = "(".concat(getTypeConfiguration(invocationCtx, type).getIdAttributeName()).concat("=").concat(name).concat(")");
            sr = searchIdentityObjects(invocationCtx,
               entryCtxs,
               filter,
               null,
               new String[]{getTypeConfiguration(invocationCtx, type).getIdAttributeName()},
               null);
         }

         //log.debug("Search filter: " + filter);

         if (sr.size() > 1)
         {
            throw new IdentityException("Found more than one identity object with name: " + name +
               "; Posible data inconsistency");
         }
         SearchResult res = (SearchResult)sr.iterator().next();
         ctx = (Context)res.getObject();
         String dn = ctx.getNameInNamespace();
         IdentityObject io = createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn);
         ctx.close();
         return io;

      }
      catch (NoSuchElementException e)
      {
         //log.debug("No identity object found with name: " + name, e);
      }
      catch (NamingException e)
      {
         throw new IdentityException("IdentityObject search failed.", e);
      }
      finally
      {
         try
         {
            if (ctx != null)
            {
               ctx.close();
            }
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }

      return null;
   }

   public IdentityObject findIdentityObject(IdentityStoreInvocationContext ctx, String id) throws IdentityException
   {
      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".findIdentityObject with id: " + id);
      }

      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {
         if (id == null)
         {
            throw new IdentityException("identity id cannot be null");
         }

         String dn = id;

         IdentityObjectType type = null;

         //Recognize the type by ctx DN

         IdentityObjectType[] possibleTypes = getConfiguration(ctx).getConfiguredTypes();

         for (IdentityObjectType possibleType : possibleTypes)
         {
            String[] typeCtxs = getTypeConfiguration(ctx, possibleType).getCtxDNs();

            for (String typeCtx : typeCtxs)
            {
               if (dn.endsWith(typeCtx))
               {
                  type = possibleType;
                  break;
               }
            }
            if (type != null)
            {
               break;
            }
         }

         if (type == null)
         {
            throw new IdentityException("Cannot recognize identity object type by its DN: " + dn);
         }

         // Grab entry

         Attributes attrs = ldapContext.getAttributes(dn);

         if (attrs == null)
         {
            throw new IdentityException("Can't find identity entry with DN: " + dn);
         }

         return createIdentityObjectInstance(ctx, type, attrs, dn);

      }
      catch (NoSuchElementException e)
      {
         //log.debug("No identity object found with dn: " + dn, e);
      }
      catch (NamingException e)
      {
         throw new IdentityException("Identity object search failed.", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
      return null;
   }

   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCtx,
                                                        IdentityObjectType type,
                                                        IdentityObjectSearchCriteria constraints) throws IdentityException
   {

      //TODO: page control with LDAP request control



      String nameFilter = "*";

      //Filter by name
      if (constraints != null  && constraints.getFilter() != null)
      {
         nameFilter = constraints.getFilter();
      }


      LdapContext ctx = getLDAPContext(invocationCtx);


      checkIOType(type);

      LinkedList<IdentityObject> objects = new LinkedList<IdentityObject>();

      LDAPIdentityObjectTypeConfiguration typeConfiguration = getTypeConfiguration(invocationCtx, type);

      try
      {
         Control[] requestControls = null;

         // Sort control
         if (constraints != null && constraints.isSorted())
         {
            //TODO: make criticallity optional
            //TODO sort by attribute name
            requestControls = new Control[]{
               new SortControl(typeConfiguration.getIdAttributeName(), Control.NONCRITICAL)
            };
         }

         StringBuilder af = new StringBuilder();

         // Filter by attribute values
         if (constraints != null && constraints.isFiltered())
         {
            af.append("(&");

            for (Map.Entry<String, String[]> stringEntry : constraints.getValues().entrySet())
            {
               for (String value : stringEntry.getValue())
               {
                  af.append("(")
                     .append(stringEntry.getKey())
                     .append("=")
                     .append(value)
                     .append(")");
               }
            }

            af.append(")");
         }

         String filter = getTypeConfiguration(invocationCtx, type).getEntrySearchFilter();
         List<SearchResult> sr = null;

         String[] entryCtxs = getTypeConfiguration(invocationCtx, type).getCtxDNs();

         if (filter != null && filter.length() > 0)
         {

            Object[] filterArgs = {nameFilter};
            sr = searchIdentityObjects(invocationCtx,
               entryCtxs,
               "(&(" + filter + ")" + af.toString() + ")",
               filterArgs,
               new String[]{typeConfiguration.getIdAttributeName()},
               requestControls);
         }
         else
         {
            filter = "(".concat(typeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
            sr = searchIdentityObjects(invocationCtx,
               entryCtxs,
               "(&(" + filter + ")" + af.toString() + ")",
               null,
               new String[]{typeConfiguration.getIdAttributeName()},
               requestControls);
         }


         for (SearchResult res : sr)
         {
            ctx = (LdapContext)res.getObject();
            String dn = ctx.getNameInNamespace();
            if (constraints != null && constraints.isSorted())
            {
               // It seams that the sort order is not configurable and
               // sort control returns entries in descending order by default...
               if (!constraints.isAscending())
               {
                  objects.addFirst(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
               }
               else
               {
                  objects.addLast(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
               }
            }
            else
            {
               objects.add(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
            }
         }

         ctx.close();


      }
      catch (NoSuchElementException e)
      {
         //log.debug("No identity object found with name: " + name, e);
      }
      catch (Exception e)
      {
         throw new IdentityException("IdentityObject search failed.", e);
      }
      finally
      {
         try
         {
            if (ctx != null)
            {
               ctx.close();
            }
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }

      if (constraints != null && constraints.isPaged())
      {
         objects = (LinkedList)cutPageFromResults(objects, constraints);
      }

      return objects;
   }

   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObjectType type) throws IdentityException
   {
      return findIdentityObject(invocationCtx, type, null);
   }


   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext ctx,
                                                        IdentityObject identity,
                                                        IdentityObjectRelationshipType relationshipType,
                                                        boolean parent,
                                                        IdentityObjectSearchCriteria constraints) throws IdentityException
   {

      //TODO: relationshipType is ignored - maybe check and allow only MEMBERSHIP?



      LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, identity);

      LDAPIdentityObjectTypeConfiguration typeConfig = getTypeConfiguration(ctx, identity.getIdentityType());

      LdapContext ldapContext = getLDAPContext(ctx);

      List<IdentityObject> objects = new LinkedList<IdentityObject>();

      try
      {

         // If parent simply look for all its members
         if (parent)
         {
            if (typeConfig.getMembershipAttributeName() == null)
            {
               throw new IdentityException("Membership attribute name not configured. Given IdentityObjectType cannot have" +
                  "members: " + identity.getIdentityType().getName());
            }

            Attributes attrs = ldapContext.getAttributes(ldapFromIO.getDn());
            Attribute member = attrs.get(typeConfig.getMembershipAttributeName());

            if (member != null)
            {
               NamingEnumeration memberValues = member.getAll();
               while (memberValues.hasMoreElements())
               {
                  String memberRef = memberValues.nextElement().toString();

                  if (typeConfig.isMembershipAttributeDN())
                  {
                     //TODO: use direct LDAP query instaed of other find method and add attributesFilter

                     if (constraints != null && constraints.getFilter() != null)
                     {
                        String name = Tools.stripDnToName(memberRef);
                        String regex = Tools.wildcardToRegex(constraints.getFilter());

                        if (Pattern.matches(regex, name))
                        {
                           objects.add(findIdentityObject(ctx, memberRef));
                        }
                     }
                     else
                     {
                        objects.add(findIdentityObject(ctx, memberRef));
                     }
                  }
                  else
                  {
                     //TODO: if relationships are not refered with DNs and only names its not possible to map
                     //TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
                     throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
                        "and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
                        "Workaround needed");
                  }
                  //break;
               }
            }
         }

         // if not parent then all parent entries need to be found
         else
         {
            // Search in all other type contexts
            for (IdentityObjectType parentType : configuration.getConfiguredTypes())
            {
               checkIOType(parentType);

               LDAPIdentityObjectTypeConfiguration parentTypeConfiguration = getTypeConfiguration(ctx, parentType);

               List<String> allowedTypes = Arrays.asList(parentTypeConfiguration.getAllowedMembershipTypes());

               // Check if given identity type can be parent
               if (!allowedTypes.contains(identity.getIdentityType().getName()))
               {
                  continue;
               }

               String nameFilter = "*";

               //Filter by name
               if (constraints != null && constraints.getFilter() != null)
               {
                  nameFilter = constraints.getFilter();
               }

               Control[] requestControls = null;

               StringBuilder af = new StringBuilder();

               // Filter by attribute values
               if (constraints != null && constraints.isFiltered())
               {
                  af.append("(&");

                  for (Map.Entry<String, String[]> stringEntry : constraints.getValues().entrySet())
                  {
                     for (String value : stringEntry.getValue())
                     {
                        af.append("(")
                           .append(stringEntry.getKey())
                           .append("=")
                           .append(value)
                           .append(")");
                     }
                  }

                  af.append(")");
               }

               // Add filter to search only parents of the given entry
               af.append("(")
                  .append(parentTypeConfiguration.getMembershipAttributeName())
                  .append("=");
               if (parentTypeConfiguration.isMembershipAttributeDN())
               {
                  af.append(ldapFromIO.getDn());
               }
               else
               {
                  //TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
                  af.append(ldapFromIO.getName());
               }
               af.append(")");


               String filter = parentTypeConfiguration.getEntrySearchFilter();
               List<SearchResult> sr = null;

               String[] entryCtxs = parentTypeConfiguration.getCtxDNs();

               if (filter != null && filter.length() > 0)
               {

                  Object[] filterArgs = {nameFilter};
                  sr = searchIdentityObjects(ctx,
                     entryCtxs,
                     "(&(" + filter + ")" + af.toString() + ")",
                     filterArgs,
                     new String[]{parentTypeConfiguration.getIdAttributeName()},
                     requestControls);
               }
               else
               {
                  filter = "(".concat(parentTypeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
                  sr = searchIdentityObjects(ctx,
                     entryCtxs,
                     "(&(" + filter + ")" + af.toString() + ")",
                     null,
                     new String[]{parentTypeConfiguration.getIdAttributeName()},
                     requestControls);
               }

               for (SearchResult res : sr)
               {
                  LdapContext ldapCtx = (LdapContext)res.getObject();
                  String dn = ldapCtx.getNameInNamespace();

                  objects.add(createIdentityObjectInstance(ctx, parentType, res.getAttributes(), dn));
               }
            }


         }

      }
      catch (NamingException e)
      {
         throw new IdentityException("Failed to resolve relationship", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }

      if (constraints != null && constraints.isPaged())
      {
         objects = cutPageFromResults(objects, constraints);
      }

      if (constraints != null && constraints.isSorted())
      {
         sortByName(objects, constraints.isAscending());
      }

      return objects;
   }

   public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext ctx,
                                                               IdentityObject identity,
                                                               IdentityObjectRelationshipType type,
                                                               boolean parent,
                                                               boolean named,
                                                               String name) throws IdentityException
   {
      //TODO: relationshipType is ignored - maybe check and allow only MEMBERSHIP?



      LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identity);

      LDAPIdentityObjectTypeConfiguration typeConfig = getTypeConfiguration(ctx, identity.getIdentityType());

      LdapContext ldapContext = getLDAPContext(ctx);

      Set<IdentityObjectRelationship> relationships = new HashSet<IdentityObjectRelationship>();

      try
      {

         // If parent simply look for all its members
         if (parent)
         {
            Attributes attrs = ldapContext.getAttributes(ldapIO.getDn());
            Attribute member = attrs.get(typeConfig.getMembershipAttributeName());

            if (member != null)
            {
               NamingEnumeration memberValues = member.getAll();
               while (memberValues.hasMoreElements())
               {
                  String memberRef = memberValues.nextElement().toString();

                  if (typeConfig.isMembershipAttributeDN())
                  {
                     //TODO: use direct LDAP query instaed of other find method and add attributesFilter


                     relationships.add(new LDAPIdentityObjectRelationshipImpl(null, ldapIO, findIdentityObject(ctx, memberRef)));

                  }
                  else
                  {
                     //TODO: if relationships are not refered with DNs and only names its not possible to map
                     //TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
                     throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
                        "and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
                        "Workaround needed");
                  }
                  //break;
               }
            }
         }

         // if not parent then all parent entries need to be found
         else
         {
            // Search in all other type contexts
            for (IdentityObjectType parentType : configuration.getConfiguredTypes())
            {
               checkIOType(parentType);

               LDAPIdentityObjectTypeConfiguration parentTypeConfiguration = getTypeConfiguration(ctx, parentType);

               List<String> allowedTypes = Arrays.asList(parentTypeConfiguration.getAllowedMembershipTypes());

               // Check if given identity type can be parent
               if (!allowedTypes.contains(identity.getIdentityType().getName()))
               {
                  continue;
               }

               String nameFilter = "*";

               //Filter by name
               Control[] requestControls = null;

               StringBuilder af = new StringBuilder();


               // Add filter to search only parents of the given entry
               af.append("(")
                  .append(parentTypeConfiguration.getMembershipAttributeName())
                  .append("=");
               if (parentTypeConfiguration.isMembershipAttributeDN())
               {
                  af.append(ldapIO.getDn());
               }
               else
               {
                  //TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
                  af.append(ldapIO.getName());
               }
               af.append(")");


               String filter = parentTypeConfiguration.getEntrySearchFilter();
               List<SearchResult> sr = null;

               String[] entryCtxs = parentTypeConfiguration.getCtxDNs();

               if (filter != null && filter.length() > 0)
               {

                  Object[] filterArgs = {nameFilter};
                  sr = searchIdentityObjects(ctx,
                     entryCtxs,
                     "(&(" + filter + ")" + af.toString() + ")",
                     filterArgs,
                     new String[]{parentTypeConfiguration.getIdAttributeName()},
                     requestControls);
               }
               else
               {
                  filter = "(".concat(parentTypeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
                  sr = searchIdentityObjects(ctx,
                     entryCtxs,
                     "(&(" + filter + ")" + af.toString() + ")",
                     null,
                     new String[]{parentTypeConfiguration.getIdAttributeName()},
                     requestControls);
               }

               for (SearchResult res : sr)
               {
                  LdapContext ldapCtx = (LdapContext)res.getObject();
                  String dn = ldapCtx.getNameInNamespace();
                 
                  relationships.add(new LDAPIdentityObjectRelationshipImpl(null, createIdentityObjectInstance(ctx, parentType, res.getAttributes(), dn), ldapIO));
               }
            }


         }

      }
      catch (NamingException e)
      {
         throw new IdentityException("Failed to resolve relationship", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }


      return relationships;
   }

   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext ctx,
                                                        IdentityObject identity,
                                                        IdentityObjectRelationshipType relationshipType,
                                                        boolean parent) throws IdentityException
   {
      return findIdentityObject(ctx, identity, relationshipType, parent, null);
   }

   public IdentityObjectRelationship createRelationship(IdentityStoreInvocationContext ctx, IdentityObject fromIdentity, IdentityObject toIdentity,
                                  IdentityObjectRelationshipType relationshipType,
                                  String name, boolean createNames) throws IdentityException
   {

      //TODO: relationshipType is ignored for now

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".createRelationship with "
            + "fromIdentity: " + fromIdentity
            + "; toIdentity: " + toIdentity
            + "; relationshipType: " + relationshipType
         );
      }

      LDAPIdentityObjectRelationshipImpl relationship = null;

      LDAPIdentityObjectImpl ldapFromIO =  getSafeLDAPIO(ctx, fromIdentity);

      LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);

      LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());

      LdapContext ldapContext = getLDAPContext(ctx);

      // Check posibilities
      if (!getSupportedFeatures().isRelationshipTypeSupported(fromIdentity.getIdentityType(), toIdentity.getIdentityType(), relationshipType))
      {
         throw new IdentityException("Relationship not supported. RelationshipType[ " + relationshipType + " ] " +
            "beetween: [ " + fromIdentity.getIdentityType().getName() + " ] and [ " + toIdentity.getIdentityType().getName() + " ]");
      }

      try
      {
         // Construct new member attribute values
         Attributes attrs = new BasicAttributes(true);

         Attribute member = new BasicAttribute(fromTypeConfig.getMembershipAttributeName());

         if (fromTypeConfig.isMembershipAttributeDN())
         {
            member.add(ldapToIO.getDn());
         }
         else
         {
            member.add(toIdentity.getName());
         }

         attrs.put(member);

         ldapContext.modifyAttributes(ldapFromIO.getDn(), DirContext.ADD_ATTRIBUTE, attrs);

         relationship = new LDAPIdentityObjectRelationshipImpl(name, ldapFromIO, ldapToIO);

      }
      catch (NamingException e)
      {
         throw new IdentityException("Failed to create relationship", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }


      return relationship;
   }

   public void removeRelationship(IdentityStoreInvocationContext ctx, IdentityObject fromIdentity, IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType, String name) throws IdentityException
   {
      // relationshipType is ignored for now

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".removeRelationship with "
            + "fromIdentity: " + fromIdentity
            + "; toIdentity: " + toIdentity
            + "; relationshipType: " + relationshipType
         );
      }

      LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, fromIdentity);
      LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);

      LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());

      // If relationship is not allowed simply return
      //TODO: use features description instead
      if (!Arrays.asList(fromTypeConfig.getAllowedMembershipTypes()).contains(ldapToIO.getIdentityType().getName()))
      {
         return;
      }

      LdapContext ldapContext = getLDAPContext(ctx);

      // Check posibilities

      //TODO: null RelationshipType passed from removeRelationships
      if (relationshipType != null &&
         !getSupportedFeatures().isRelationshipTypeSupported(fromIdentity.getIdentityType(), toIdentity.getIdentityType(), relationshipType))
      {
         throw new IdentityException("Relationship not supported");
      }

      try
      {
         //construct new member attribute values
         Attributes attrs = new BasicAttributes(true);

         Attribute member = new BasicAttribute(fromTypeConfig.getMembershipAttributeName());

         if (fromTypeConfig.isMembershipAttributeDN())
         {
            member.add(ldapToIO.getDn());
         }
         else
         {
            member.add(toIdentity.getName());
         }

         attrs.put(member);

         ldapContext.modifyAttributes(ldapFromIO.getDn(), DirContext.REMOVE_ATTRIBUTE, attrs);

      }
      catch (NamingException e)
      {
         throw new IdentityException("Failed to remove relationship", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
   }

   public void removeRelationships(IdentityStoreInvocationContext ctx, IdentityObject identity1, IdentityObject identity2, boolean named) throws IdentityException
   {
      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".removeRelationships with "
            + "identity1: " + identity1
            + "; identity2: " + identity2
         );
      }

      // as relationship type is ignored in this impl for now...
      removeRelationship(ctx, identity1, identity2, null, null);
      removeRelationship(ctx, identity2, identity1, null, null);

   }

   public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext ctx, IdentityObject fromIdentity, IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType) throws IdentityException
   {
      // relationshipType is ignored for now
     

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".resolveRelationships with "
            + "fromIdentity: " + fromIdentity
            + "; toIdentity: " + toIdentity
         );
      }

      Set<IdentityObjectRelationship> relationships = new HashSet<IdentityObjectRelationship>();

      LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, fromIdentity);
      LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);

      LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());

      // If relationship is not allowed return empty set
      //TODO: use features description instead

      if (!Arrays.asList(fromTypeConfig.getAllowedMembershipTypes()).contains(ldapToIO.getIdentityType().getName()))
      {
         return relationships;
      }

      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {
         Attributes attrs = ldapContext.getAttributes(ldapFromIO.getDn());
         Attribute member = attrs.get(fromTypeConfig.getMembershipAttributeName());

         if (member != null)
         {
            NamingEnumeration memberValues = member.getAll();
            while (memberValues.hasMoreElements())
            {
               String memberRef = memberValues.nextElement().toString();

               if ((fromTypeConfig.isMembershipAttributeDN() && memberRef.equals(ldapToIO.getDn())) ||
                  (!fromTypeConfig.isMembershipAttributeDN() && memberRef.equals(ldapToIO.getName())))
               {
                  //TODO: impl lacks support for rel type
                  relationships.add(new LDAPIdentityObjectRelationshipImpl(null, ldapFromIO, ldapToIO));
               }
            }
         }

      }
      catch (NamingException e)
      {
         throw new IdentityException("Failed to resolve relationship", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
      return relationships;
   }

   public String createRelationshipName(IdentityStoreInvocationContext ctx, String name) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
   }

   public String removeRelationshipName(IdentityStoreInvocationContext ctx, String namethrows IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObjectSearchCriteria controls) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectSearchCriteria controls) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
     
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObject identity) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
   }


   public Map<String, String> getRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");

   }

   public void setRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name, Map<String, String> properties) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");

   }

   public void removeRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name, Set<String> properties) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");

   }

   public Map<String, String> getRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");

   }

   public void setRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship, Map<String, String> properties) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");

   }

   public void removeRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship, Set<String> properties) throws IdentityException, OperationNotSupportedException
   {
      throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");
     
   }

   public boolean validateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject, IdentityObjectCredential credential) throws IdentityException
   {
      if (credential == null)
      {
         throw new IllegalArgumentException();
      }

      LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identityObject);

      if (supportedFeatures.isCredentialSupported(ldapIO.getIdentityType(),credential.getType()))
      {

         String passwordString = null;

         // Handle generic impl

         if (credential.getValue() != null)
         {
            //TODO: support for empty password should be configurable
            passwordString = credential.getValue().toString();
         }
         else
         {
            throw new IdentityException("Null password value");
         }

         LdapContext ldapContext = getLDAPContext(ctx);

         try
         {

            Hashtable env = ldapContext.getEnvironment();

            env.put(Context.SECURITY_PRINCIPAL, ldapIO.getDn());
            env.put(Context.SECURITY_CREDENTIALS, passwordString);

            InitialContext initialCtx = new InitialLdapContext(env, null);

            if (initialCtx != null)
            {
               initialCtx.close();
               return true;
            }

         }
         catch (NamingException e)
         {
            //
         }
         finally
         {
            try
            {
               ldapContext.close();
            }
            catch (NamingException e)
            {
               throw new IdentityException("Failed to close LDAP connection", e);
            }
         }
         return false;


      }
      else
      {
         throw new IdentityException("CredentialType not supported for a given IdentityObjectType");
      }
   }

   public void updateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject, IdentityObjectCredential credential) throws IdentityException
   {
      if (credential == null)
      {
         throw new IllegalArgumentException();
      }

      LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identityObject);

      if (supportedFeatures.isCredentialSupported(ldapIO.getIdentityType(),credential.getType()))
      {

         String passwordString = null;

         // Handle generic impl

         if (credential.getValue() != null)
         {
            //TODO: support for empty password should be configurable
            passwordString = credential.getValue().toString();
         }
         else
         {
            throw new IdentityException("Null password value");
         }

         String attributeName = getTypeConfiguration(ctx, ldapIO.getIdentityType()).getPasswordAttributeName();

         if (attributeName == null)
         {
            throw new IdentityException("IdentityType doesn't have passwordAttributeName option set: "
               + ldapIO.getIdentityType().getName());
         }

         LdapContext ldapContext = getLDAPContext(ctx);

         try
         {
            //TODO: maybe perform a schema check if this attribute is allowed for such entry

            Attributes attrs = new BasicAttributes(true);
            Attribute attr = new BasicAttribute(attributeName);
            attr.add(passwordString);
            attrs.put(attr);

            ldapContext.modifyAttributes(ldapIO.getDn(), DirContext.REPLACE_ATTRIBUTE,attrs);
         }
         catch (NamingException e)
         {
            throw new IdentityException("Cannot set identity password value.", e);
         }
         finally
         {
            try
            {
               ldapContext.close();
            }
            catch (NamingException e)
            {
               throw new IdentityException("Failed to close LDAP connection", e);
            }
         }

      }
      else
      {
         throw new IdentityException("CredentialType not supported for a given IdentityObjectType");
      }
   }


   // Attributes

   public Set<String> getSupportedAttributeNames(IdentityStoreInvocationContext invocationContext, IdentityObjectType identityType) throws IdentityException
   {
      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".getSupportedAttributeNames with "
            + "identityType: " + identityType
         );
      }

      checkIOType(identityType);

      return getTypeConfiguration(invocationContext, identityType).getMappedAttributesNames();
   }

   public Map<String, IdentityObjectAttributeMetaData> getAttributesMetaData(IdentityStoreInvocationContext invocationContext, IdentityObjectType identityObjectType)
   {
      return attributesMetaData.get(identityObjectType.getName());
   }


   public IdentityObjectAttribute getAttribute(IdentityStoreInvocationContext invocationContext, IdentityObject identity, String name) throws IdentityException
   {
      //TODO: dummy temporary implementation
      return getAttributes(invocationContext, identity).get(name);
   }

   public Map<String, IdentityObjectAttribute> getAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity) throws IdentityException
   {

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".getAttributes with "
            + "identity: " + identity
         );
      }

      Map<String, IdentityObjectAttribute> attrsMap = new HashMap<String, IdentityObjectAttribute>();

      LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);


      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {
         Set<String> mappedNames = getTypeConfiguration(ctx, identity.getIdentityType()).getMappedAttributesNames();

         // as this is valid LDAPIdentityObjectImpl DN is obtained from the Id

         String dn = ldapIdentity.getDn();

         Attributes attrs = ldapContext.getAttributes(dn);

         for (Iterator iterator = mappedNames.iterator(); iterator.hasNext();)
         {
            String name = (String)iterator.next();
            String attrName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);
            Attribute attr = attrs.get(attrName);

            if (attr != null)
            {

               IdentityObjectAttribute identityObjectAttribute = new SimpleAttribute(name);

               NamingEnumeration values = attr.getAll();

               while (values.hasMoreElements())
               {
                  identityObjectAttribute.addValue(values.nextElement().toString());
               }

               attrsMap.put(name, identityObjectAttribute);
            }
            else
            {
               log.fine("No such attribute ('" + attrName + "') in entry: " + dn);
            }
         }
      }
      catch (NamingException e)
      {
         throw new IdentityException("Cannot get attributes value.", e);
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }

      return attrsMap;

   }

   public void updateAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectAttribute[] attributes) throws IdentityException
   {

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".updateAttributes with "
            + "identity: " + identity
            + "attributes: " + attributes
         );
      }

      if (attributes == null)
      {
         throw new IllegalArgumentException("attributes is null");
      }

      LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);


      // as this is valid LDAPIdentityObjectImpl DN is obtained from the Id

      String dn = ldapIdentity.getDn();

      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {

         for (IdentityObjectAttribute attribute : attributes)
         {
            String name = attribute.getName();

            String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);

            if (attributeName == null)
            {
               log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
               continue;
            }

            //TODO: maybe perform a schema check if this attribute is not required

            Attributes attrs = new BasicAttributes(true);
            Attribute attr = new BasicAttribute(attributeName);

            Collection values = attribute.getValues();

            Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());

            if (mdMap != null)
            {
               IdentityObjectAttributeMetaData amd = mdMap.get(attributeName);
               if (amd != null && !amd.isMultivalued() && values.size() > 1)
               {
                  throw new IdentityException("Cannot assigned multiply values to single valued attribute: " + attributeName);
               }
               if (amd != null && amd.isReadonly())
               {
                  throw new IdentityException("Cannot update readonly attribute: " + attributeName);
               }
         }

            if (values != null)
            {
               for (Object value : values)
               {
                  attr.add(value);
               }

               attrs.put(attr);

               try
               {
                  ldapContext.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
               }
               catch (NamingException e)
               {
                  throw new IdentityException("Cannot add attribute", e);
               }
            }

         }
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
   }

   public void addAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectAttribute[] attributes) throws IdentityException
   {

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".addAttributes with "
            + "identity: " + identity
            + "attributes: " + attributes
         );
      }


      if (attributes == null)
      {
         throw new IllegalArgumentException("attributes is null");
      }

      LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);


      // as this is valid LDAPIdentityObjectImpl DN is obtained from the Id

      String dn = ldapIdentity.getDn();

      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {
         for (IdentityObjectAttribute attribute : attributes)
         {
            String name = attribute.getName();

            String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);

            if (attributeName == null)
            {
               log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
               continue;
            }

            //TODO: maybe perform a schema check if this attribute is not required

            Attributes attrs = new BasicAttributes(true);
            Attribute attr = new BasicAttribute(attributeName);

            Collection values = attribute.getValues();

            Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());

            if (mdMap != null)
            {
               IdentityObjectAttributeMetaData amd = mdMap.get(attributeName);
               if (amd != null && !amd.isMultivalued() && values.size() > 1)
               {
                  throw new IdentityException("Cannot assigned multiply values to single valued attribute: " + attributeName);
               }
               if (amd != null && amd.isReadonly())
               {
                  throw new IdentityException("Cannot update readonly attribute: " + attributeName);
               }
            }


            if (values != null)
            {
               for (Object value : values)
               {
                  attr.add(value);
               }

               attrs.put(attr);

               try
               {
                  ldapContext.modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, attrs);
               }
               catch (NamingException e)
               {
                  throw new IdentityException("Cannot add attribute", e);
               }
            }

         }
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
   }

   public void removeAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, String[] attributeNames) throws IdentityException
   {

      if (log.isLoggable(Level.FINER))
      {
         log.finer(toString() + ".removeAttributes with "
            + "identity: " + identity
            + "attributeNames: " + attributeNames
         );
      }

      if (attributeNames == null)
      {
         throw new IllegalArgumentException("attributes is null");
      }

      LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);

      // as this is valid LDAPIdentityObjectImpl DN is obtained from the Id

      String dn = ldapIdentity.getDn();

      LdapContext ldapContext = getLDAPContext(ctx);

      try
      {
         for (String name : attributeNames)
         {
            String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);

            if (attributeName == null)
            {
               log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
               continue;
            }

            Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());

            if (mdMap != null)
            {
               //TODO: maybe perform a schema check if this attribute is not required on the LDAP level
               IdentityObjectAttributeMetaData amd = mdMap.get(name);
               if (amd != null && amd.isRequired())
               {
                  throw new IdentityException("Cannot remove required attribute: " + name);
               }
            }



            Attributes attrs = new BasicAttributes(true);
            Attribute attr = new BasicAttribute(attributeName);
            attrs.put(attr);

            try
            {
               ldapContext.modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, attrs);
            }
            catch (NamingException e)
            {
               throw new IdentityException("Cannot remove attribute", e);
            }

         }
      }
      finally
      {
         try
         {
            ldapContext.close();
         }
         catch (NamingException e)
         {
            throw new IdentityException("Failed to close LDAP connection", e);
         }
      }
   }

   //Internal

   public LDAPIdentityObjectImpl createIdentityObjectInstance(IdentityStoreInvocationContext ctx, IdentityObjectType type, Attributes attrs, String dn) throws IdentityException
   {
      LDAPIdentityObjectImpl ldapio = null;
      try
      {
         String idAttrName = getTypeConfiguration(ctx, type).getIdAttributeName();

         Attribute ida = attrs.get(idAttrName);
         if (ida == null)
         {
            throw new IdentityException("LDAP entry doesn't contain proper attribute:" + idAttrName);
         }

         //make DN as user ID
         ldapio = new LDAPIdentityObjectImpl(dn, ida.get().toString(), type);

      }
      catch (Exception e)
      {
         throw new IdentityException("Couldn't create LDAPIdentityObjectImpl object from ldap entry (SearchResult)", e);
      }

      return ldapio;
   }

   public List<SearchResult> searchIdentityObjects(IdentityStoreInvocationContext ctx,
                                                   String[] entryCtxs,
                                                   String filter,
                                                   Object[] filterArgs,
                                                   String[] returningAttributes,
                                                   Control[] requestControls) throws NamingException, IdentityException
   {

      LdapContext ldapContext = getLDAPContext(ctx);

      if (ldapContext != null)
      {
         ldapContext.setRequestControls(requestControls);
      }

      NamingEnumeration results = null;

      try
      {

         SearchControls searchControls = new SearchControls();
         searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
         searchControls.setReturningObjFlag(true);
         searchControls.setTimeLimit(getConfiguration(ctx).getSearchTimeLimit());


         if (returningAttributes != null)
         {
            searchControls.setReturningAttributes(returningAttributes);
         }


         if (entryCtxs.length == 1)
         {
            if (filterArgs == null)
            {
               results = ldapContext.search(entryCtxs[0], filter, searchControls);
            }
            else
            {
               results = ldapContext.search(entryCtxs[0], filter, filterArgs, searchControls);
            }
            return Tools.toList(results);


         }
         else
         {
            List<SearchResult> merged = new LinkedList();

            for (String entryCtx : entryCtxs)
            {
               if (filterArgs == null)
               {
                  results = ldapContext.search(entryCtx, filter, searchControls);
               }
               else
               {
                  results = ldapContext.search(entryCtx, filter, filterArgs, searchControls);
               }
               merged.addAll(Tools.toList(results));
               results.close();
            }

            return merged;
         }
      }
      finally
      {
         if (results != null)
         {
            results.close();
         }
         ldapContext.close();
      }
   }

   // HELPER

   private LDAPIdentityObjectImpl getSafeLDAPIO(IdentityStoreInvocationContext ctx, IdentityObject io) throws IdentityException
   {
      if (io == null)
      {
         throw new IllegalArgumentException("IdentityObject is null");
      }

      if (io instanceof LDAPIdentityObjectImpl)
      {
         return (LDAPIdentityObjectImpl)io;
      }
      else
      {
         try
         {
            return (LDAPIdentityObjectImpl)findIdentityObject(ctx, io.getName(), io.getIdentityType());
         }
         catch (IdentityException e)
         {
            throw new IdentityException("Provided IdentityObject is not present in the store. Cannot operate on not stored objects.", e);
         }
      }

   }

   private void checkIOType(IdentityObjectType iot) throws IdentityException
   {
      if (iot == null)
      {
         throw new IllegalArgumentException("IdentityObjectType is null");
      }

      if (!getSupportedFeatures().isIdentityObjectTypeSupported(iot))
      {
         throw new IdentityException("IdentityType not supported by this IdentityStore implementation: " + iot);
      }
   }


   private LdapContext getLDAPContext(IdentityStoreInvocationContext ctx) throws IdentityException
   {

      LdapContext ldapContext = null;

      try
      {
         ldapContext = (LdapContext)ctx.getIdentityStoreSession().getSessionContext();
      }
      catch (Exception e)
      {
         throw new IdentityException("Could not obtain LDAP connection: ", e);
      }
     
      if (ldapContext == null)
      {
         throw new IdentityException("IllegalState: - Could not obtain LDAP connection");
      }

      return ldapContext;
   }

   private LDAPIdentityStoreConfiguration getConfiguration(IdentityStoreInvocationContext ctx) throws IdentityException
   {
      return configuration;
   }

   private LDAPIdentityObjectTypeConfiguration getTypeConfiguration(IdentityStoreInvocationContext ctx, IdentityObjectType type) throws IdentityException
   {
      return getConfiguration(ctx).getTypeConfiguration(type.getName());
   }

   public String toString()
   {
      return this.getClass().getName() + "[" + getId() +"]";
   }

   private void sortByName(List<IdentityObject> objects, final boolean ascending)
   {
      Collections.sort(objects, new Comparator<IdentityObject>(){
         public int compare(IdentityObject o1, IdentityObject o2)
         {
            if (ascending)
            {
               return o1.getName().compareTo(o2.getName());
            }
            else
            {
               return o2.getName().compareTo(o1.getName());  
            }
         }
      });
   }

   //TODO: dummy and inefficient temporary workaround. Need to be implemented with ldap request control
   private List<IdentityObject> cutPageFromResults(List<IdentityObject> objects, IdentityObjectSearchCriteria constraints)
   {
      List<IdentityObject> results = new LinkedList<IdentityObject>();
      for (int i = constraints.getFirstResult(); i < constraints.getFirstResult() + constraints.getMaxResults(); i++)
      {
         if (i < objects.size())
         {
            results.add(objects.get(i));
         }
      }
      return results;
   }

}
TOP

Related Classes of org.jboss.identity.idm.impl.store.ldap.LDAPIdentityStoreImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.