001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: AMIdentityRepository.java,v 1.21 2010/01/06 01:58:26 veiming Exp $
026 *
027 * Portions Copyrighted 2011-2016 ForgeRock AS.
028 */
029package com.sun.identity.idm;
030
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.HashSet;
035import java.util.Iterator;
036import java.util.Map;
037import java.util.Set;
038
039import javax.inject.Inject;
040import javax.security.auth.callback.Callback;
041
042import org.forgerock.openam.ldap.LDAPUtils;
043
044import com.google.inject.assistedinject.Assisted;
045import com.iplanet.am.sdk.AMHashMap;
046import com.iplanet.sso.SSOException;
047import com.iplanet.sso.SSOToken;
048import com.sun.identity.common.CaseInsensitiveHashMap;
049import com.sun.identity.common.DNUtils;
050import com.sun.identity.shared.debug.Debug;
051import com.sun.identity.sm.DNMapper;
052import com.sun.identity.sm.OrganizationConfigManager;
053import com.sun.identity.sm.SMSException;
054import org.forgerock.openam.utils.CrestQuery;
055
056/**
057 * The class <code> AMIdentityRepository </code> represents an object to access
058 * the repositories in which user/role/group and other identity data is
059 * configured. This class provides access to methods which will search, create
060 * and delete identities. An instance of this class can be obtained in the
061 * following manner:
062 * <p>
063 *
064 * <PRE>
065 *
066 * AMIdentityRepository idRepo = new AMIdentityRepository(ssoToken, realmName);
067 *
068 * </PRE>
069 *
070 * @supported.api
071 */
072public class AMIdentityRepository {
073    private SSOToken token;
074    private String organizationDN;
075    private String idRealmName;
076
077    public static Debug debug = Debug.getInstance("amIdm");
078    public static Map listeners = new CaseInsensitiveHashMap();
079
080    private static Set<IdRepoCreationListener> creationListeners = new HashSet<IdRepoCreationListener>();
081
082    /**
083     * @supported.api
084     *
085     * Constructor for the <code>AMIdentityRepository</code> object. If a null
086     * is passed for the organization identifier <code>realmName</code>, then
087     * the "root" realm is assumed.
088     *
089     * @param ssotoken
090     *            Single sign on token of the user
091     * @param realmName
092     *            Name of the realm (can be a Fully qualified DN)
093     * @throws IdRepoException Never thrown, required by legacy code.
094     * @throws SSOException Never thrown, required by legacy code.
095     * @deprecated in 13.0.0, use {@link #AMIdentityRepository(String, com.iplanet.sso.SSOToken)} instead
096     */
097    @Deprecated
098    public AMIdentityRepository(SSOToken ssotoken, String realmName)
099            throws IdRepoException, SSOException {
100        this(realmName, ssotoken);
101    }
102
103    /**
104     * @supported.api
105     *
106     * Constructor for the {@code AMIdentityRepository} object. If a {@code null} is passed for
107     * the organization identifier {@code realmName}, then the "root" realm is assumed.
108     *
109     * @param ssoToken Single sign on token of the user.
110     * @param realmName Name of the realm (can be a Fully qualified DN).
111     */
112    @Inject
113    public AMIdentityRepository(@Assisted String realmName, @Assisted SSOToken ssoToken) {
114        token = ssoToken;
115        idRealmName = realmName;
116        organizationDN = DNMapper.orgNameToDN(realmName);
117        notifyCreationListeners();
118    }
119
120    /**
121     * Adds a creation listener that will be notified each time a {@code AMIdentityRepository} is created .
122     *
123     * @param listener The listener.
124     */
125    public static void addCreationListener(IdRepoCreationListener listener) {
126        creationListeners.add(listener);
127    }
128
129    /**
130     * Removes a creation listener so that it will no longer be notified when a
131     * {@code AMIdentityRepository} is created.
132     *
133     * @param listener The listener.
134     * @return {@code true} if the listener was removed.
135     */
136    public static boolean removeCreationListener(IdRepoCreationListener listener) {
137        return creationListeners.remove(listener);
138    }
139
140    private void notifyCreationListeners() {
141        for (IdRepoCreationListener listener : creationListeners) {
142            listener.notify(this, idRealmName);
143        }
144    }
145
146    /**
147     * @supported.api
148     *
149     * Returns the set of supported object types <code>IdType</code> for this
150     * deployment. This is not realm specific.
151     *
152     * @return Set of supported <code> IdType </code> objects.
153     * @throws IdRepoException
154     *             if there are repository related error conditions.
155     * @throws SSOException
156     *             if user's single sign on token is invalid.
157     */
158    public Set getSupportedIdTypes() throws IdRepoException, SSOException {
159        IdServices idServices = IdServicesFactory.getDataStoreServices();
160        Set res = idServices.getSupportedTypes(token, organizationDN);
161        res.remove(IdType.REALM);
162        return res;
163    }
164
165    /**
166     * @supported.api
167     *
168     * Returns the set of Operations for a given <code>IdType</code>,
169     * <code>IdOperations</code> that can be performed on an Identity. This
170     * varies for each organization (and each plugin?).
171     *
172     * @param type
173     *            Type of identity
174     * @return Set of <code>IdOperation</code> objects.
175     * @throws IdRepoException
176     *             if there are repository related error conditions.
177     * @throws SSOException
178     *             if user's single sign on token is invalid.
179     */
180    public Set getAllowedIdOperations(IdType type) throws IdRepoException,
181            SSOException {
182        IdServices idServices = IdServicesFactory.getDataStoreServices();
183        return idServices.getSupportedOperations(token, type, organizationDN);
184    }
185
186    /**
187     *
188     * Return the special identities for this realm for a given type. These
189     * identities cannot be deleted and hence have to be shown in the admin
190     * console as non-deletable.
191     *
192     * @param type
193     *            Type of the identity
194     * @return IdSearchResult
195     * @throws IdRepoException
196     *             if there is a datastore exception
197     * @throws SSOException
198     *             if the user's single sign on token is not valid.
199     */
200    public IdSearchResults getSpecialIdentities(IdType type)
201            throws IdRepoException, SSOException {
202
203        IdSearchResults results = getSpecialIdentities(token, type,
204                organizationDN);
205
206        if (type.equals(IdType.USER)) {
207            // Iterating through to get out the names and remove only amadmin
208            // anonymous as per AM console requirement.
209
210            IdSearchResults newResults = new IdSearchResults(type,
211                    organizationDN);
212            Set identities = results.getSearchResults();
213            if ((identities != null) && !identities.isEmpty()) {
214                for (Iterator i = identities.iterator(); i.hasNext();) {
215                    AMIdentity amid = ((AMIdentity) i.next());
216                    String remUser = amid.getName().toLowerCase();
217                    if (!remUser.equalsIgnoreCase(IdConstants.AMADMIN_USER)
218                            && !remUser.equalsIgnoreCase(
219                                    IdConstants.ANONYMOUS_USER))
220                    {
221                        newResults.addResult(amid, Collections.EMPTY_MAP);
222                    }
223                }
224                results = newResults;
225            }
226        }
227        return results;
228    }
229
230    /**
231     * Searches for identities of a certain type. The iterator returns
232     * AMIdentity objects for use by the application.
233     *
234     * @deprecated This method is deprecated. Use
235     *             {@link #searchIdentities(IdType type,String pattern,
236     *             IdSearchControl ctrl)}
237     * @param type
238     *            Type of identity being searched for.
239     * @param pattern
240     *            Search pattern, like "a*" or "*".
241     * @param avPairs
242     *            Map of attribute-values which can further help qualify the
243     *            search pattern.
244     * @param recursive
245     *            If true, then the search is performed on the entire subtree
246     *            (if applicable)
247     * @param maxResults
248     *            Maximum number of results to be returned. A -1 means no limit
249     *            on the result set.
250     * @param maxTime
251     *            Maximum amount of time after which the search should return
252     *            with partial results.
253     * @param returnAttributes
254     *            Set of attributes to be read when performing the search.
255     * @param returnAllAttributes
256     *            If true, then read all the attributes of the entries.
257     * @return results containing <code>AMIdentity</code> objects.
258     * @throws IdRepoException
259     *             if there are repository related error conditions.
260     * @throws SSOException
261     *             if user's single sign on token is invalid.
262     */
263    public IdSearchResults searchIdentities(IdType type, String pattern,
264            Map avPairs, boolean recursive, int maxResults, int maxTime,
265            Set returnAttributes, boolean returnAllAttributes)
266            throws IdRepoException, SSOException {
267
268        IdSearchControl crtl = new IdSearchControl();
269        crtl.setSearchModifiers(IdSearchOpModifier.OR, avPairs);
270        crtl.setRecursive(recursive);
271        crtl.setMaxResults(maxResults);
272        crtl.setTimeOut(maxTime);
273        crtl.setReturnAttributes(returnAttributes);
274        crtl.setAllReturnAttributes(returnAllAttributes);
275
276        // Call search method that takes IdSearchControl
277        CrestQuery crestQuery = new CrestQuery(pattern);
278        return searchIdentities(type, crestQuery, crtl);
279    }
280
281    /**
282     * @supported.api
283     *
284     * Searches for identities of certain types from each plugin and returns a
285     * combined result.
286     *
287     * <b>Note:</b> The AMIdentity objects representing IdType.REALM can be
288     * used for services related operations only. The realm <code>AMIdentity
289     * </code> object can be used to assign and unassign services containing
290     * dynamic attributes to this realm.
291     *
292     * @param type
293     *            Type of identity being searched for.
294     * @param pattern
295     *            Pattern to be used when searching.
296     * @param ctrl
297     *            IdSearchControl which can be used to set up various search
298     *            controls on the search to be performed.
299     * @return Returns the combined results in an object IdSearchResults.
300     * @see com.sun.identity.idm.IdSearchControl
301     * @see com.sun.identity.idm.IdSearchResults
302     * @throws IdRepoException
303     *             if there are repository related error conditions.
304     * @throws SSOException
305     *             if user's single sign on token is invalid.
306     */
307    public IdSearchResults searchIdentities(IdType type, String pattern, IdSearchControl ctrl)
308            throws IdRepoException, SSOException {
309
310        CrestQuery crestQuery = new CrestQuery(pattern);
311        return searchIdentities(type, crestQuery, ctrl);
312    }
313
314    /**
315     * Searches for identities of certain types from each plugin and returns a
316     * combined result
317     *
318     * <b>Note:</b> The AMIdentity objects representing IdType.REALM can be
319     * used for services related operations only. The realm <code>AMIdentity
320     * </code> object can be used to assign and unassign services containing
321     * dynamic attributes to this realm.
322     *
323     * @param type
324     *            Type of identity being searched for.
325     * @param crestQuery
326     *            Basically just an object which supports both _queryId and _queryFilter
327     * @param ctrl
328     *            IdSearchControl which can be used to set up various search
329     *            controls on the search to be performed.
330     * @return Returns the combined results in an object IdSearchResults.
331     * @see com.sun.identity.idm.IdSearchControl
332     * @see com.sun.identity.idm.IdSearchResults
333     * @throws IdRepoException
334     *             if there are repository related error conditions.
335     * @throws SSOException
336     *             if user's single sign on token is invalid.
337     */
338    public IdSearchResults searchIdentities(IdType type, CrestQuery crestQuery, IdSearchControl ctrl)
339            throws IdRepoException, SSOException {
340
341        IdSearchResults idSearchResults = null;
342
343        if (type.equals(IdType.REALM)) {
344
345            if (crestQuery.hasQueryFilter()) {
346                throw new IdRepoException("realm searchIdentities does not support query filters");
347            }
348
349            try {
350                idSearchResults = new IdSearchResults(type, idRealmName);
351                OrganizationConfigManager orgMgr =
352                    new OrganizationConfigManager(token, idRealmName);
353
354                // Realm searches only support _queryId
355                String pattern = crestQuery.getQueryId();
356
357                Set realmNames = orgMgr.getSubOrganizationNames(pattern, false);
358                if (realmNames != null) {
359                    Iterator iterator = realmNames.iterator();
360                    while (iterator.hasNext()) {
361                        String realmName = (String) iterator.next();
362
363                        AMIdentity realmIdentity = getSubRealmIdentity(realmName);
364                        Map attributes = new HashMap();
365
366                        idSearchResults.addResult(realmIdentity, attributes);
367                        idSearchResults.setErrorCode(IdSearchResults.SUCCESS);
368                    }
369                }
370            } catch (SMSException sme) {
371                debug.error("AMIdentityRepository.searchIdentities() - "
372                        + "Error occurred while searching " + type.getName()
373                        + ":", sme);
374                throw new IdRepoException(sme.getMessage());
375            }
376        } else {
377            IdServices idServices = IdServicesFactory.getDataStoreServices();
378
379            idSearchResults = idServices.search(token, type, ctrl, organizationDN, crestQuery);
380        }
381        return idSearchResults;
382    }
383
384    /**
385     * @supported.api
386     *
387     * Returns a handle of the Identity object representing this
388     * realm for services related operations only. This <code> AMIdentity
389     * </code> object can be used to assign and unassign services containing
390     * dynamic attributes to this realm
391     *
392     * @return a handle of the Identity object.
393     * @throws IdRepoException
394     *             if there are repository related error conditions.
395     * @throws SSOException
396     *             if user's single sign on token is invalid.
397     */
398    public AMIdentity getRealmIdentity() throws IdRepoException, SSOException {
399        return getRealmIdentity(token, organizationDN);
400    }
401
402    private AMIdentity getRealmIdentity(SSOToken token, String orgDN)
403        throws IdRepoException {
404        String universalId = "id=ContainerDefaultTemplateRole,ou=realm," +
405            orgDN;
406        return IdUtils.getIdentity(token, universalId);
407    }
408
409    private AMIdentity getSubRealmIdentity(String subRealmName) throws
410        IdRepoException, SSOException {
411        String realmName = idRealmName;
412        if (LDAPUtils.isDN(idRealmName)) {  // Wouldn't be a DN if it starts with "/"
413            realmName = DNMapper.orgNameToRealmName(idRealmName);
414        }
415
416        String fullRealmName = realmName + IdConstants.SLASH_SEPARATOR +
417            subRealmName;
418        String subOrganizationDN = DNMapper.orgNameToDN(fullRealmName);
419
420        return getRealmIdentity(token, subOrganizationDN);
421    }
422
423    /**
424     * @supported.api
425     *
426     * Creates a single object of a type. The object is
427     * created in all the plugins that support creation of this type of object.
428     *
429     * This method is only valid for:
430     *
431     * <ol>
432     * <li> {@link IdType#AGENT IdType.AGENT} </li>
433     * <li> {@link IdType#USER  IdType.USER}  </li>
434     * <li> {@link IdType#REALM  IdType.REALM}  </li>
435     * </ol>
436     *
437     * <br>
438     * <b>Note:</b> For creating {@link IdType#REALM  IdType.REALM} identities,
439     * a map of <code>sunIdentityRepositoryService</code> attributes need to
440     * be passed. Also, AMIdentity object representing this realm can be
441     * used for services related operations only. This <code> AMIdentity
442     * </code> object can be used to assign and unassign services containing
443     * dynamic attributes to this realm
444     *
445     *
446     * @param type
447     *            <code>IdType</code> of object to be created.
448     * @param idName
449     *            Name of object. If the type is <code>IdType.REALM</code>
450     *            then enter a valid realm name.
451     * @param attrMap
452     *            Map of attribute-values to be set when creating the entry.
453     * @return Identity object representing the newly created entry.
454     * @throws IdRepoException
455     *             if there are repository related error conditions.
456     * @throws SSOException
457     *             if user's single sign on token is invalid.
458     */
459    public AMIdentity createIdentity(IdType type, String idName, Map attrMap)
460            throws IdRepoException, SSOException {
461        IdServices idServices = IdServicesFactory.getDataStoreServices();
462        return idServices.create(token, type, idName, attrMap, organizationDN);
463    }
464
465    /**
466     * @supported.api
467     *
468     * Creates multiple objects of the same type. The objects are created in all
469     * the <code>IdRepo</code> plugins that support creation of these objects.
470     *
471     * This method is only valid for:
472     *
473     * <ol>
474     * <li> {@link IdType#AGENT IdType.AGENT} </li>
475     * <li> (@link IdType#USER  IdType.USER}  </li>
476     * <li> {@link IdType#REALM  IdType.REALM}  </li>
477     * </ol>
478     *
479     * <br>
480     * <b>Note:</b> For creating {@link IdType#REALM  IdType.REALM} identities,
481     * a map of <code>sunIdentityRepositoryService</code> attributes need to
482     * be passed. Also, AMIdentity object representing this realm can be
483     * used for services related operations only. This <code> AMIdentity
484     * </code> object can be used to assign and unassign services containing
485     * dynamic attributes to this realm.
486     *
487     * @param type
488     *            Type of object to be created
489     * @param identityNamesAndAttrs
490     *            Names of the identities and their
491     * @return Set of created Identities.
492     * @throws IdRepoException
493     *             if there are repository related error conditions.
494     * @throws SSOException
495     *             if user's single sign on token is invalid.
496     */
497    public Set createIdentities(IdType type, Map identityNamesAndAttrs)
498            throws IdRepoException, SSOException {
499        Set results = new HashSet();
500
501        if (identityNamesAndAttrs == null || identityNamesAndAttrs.isEmpty()) {
502            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.ILLEGAL_ARGUMENTS, null);
503        }
504
505        Iterator it = identityNamesAndAttrs.keySet().iterator();
506
507        while (it.hasNext()) {
508            String name = (String) it.next();
509            Map attrMap = (Map) identityNamesAndAttrs.get(name);
510            AMIdentity id = createIdentity(type, name, attrMap);
511            results.add(id);
512        }
513
514        return results;
515    }
516
517    /**
518     * @supported.api
519     *
520     * Deletes identities. The Set passed is a set of <code>AMIdentity</code>
521     * objects.
522     *
523     * This method is only valid for:
524     * <ol>
525     * <li> {@link IdType#AGENT IdType.AGENT} </li>
526     * <li> {@link IdType#REALM IdType.REALM} </li>
527     * <li> (@link IdType#USER IdType.USER} </li>
528     * </ol>
529     *
530     * @param type Type of Identity to be deleted.
531     * @param identities Set of <code>AMIdentity</code> objects to be deleted.
532     * @throws IdRepoException if there are repository related error conditions.
533     * @throws SSOException if user's single sign on token is invalid.
534     * @deprecated As of release AM 7.1, replaced by
535     *             {@link #deleteIdentities(Set)}
536     */
537    public void deleteIdentities(IdType type, Set identities)
538            throws IdRepoException, SSOException {
539        deleteIdentities(identities);
540    }
541
542    /**
543     * @supported.api
544     *
545     * Deletes identities. The Set passed is a set of <code>AMIdentity</code>
546     * objects.
547     *
548     * This method is only valid for:
549     * <ol>
550     * <li> {@link IdType#AGENT IdType.AGENT} </li>
551     * <li> {@link IdType#REALM IdType.REALM} </li>
552     * <li> (@link IdType#USER  IdType.USER}  </li>
553     * </ol>
554     *
555     * @param identities Set of <code>AMIdentity</code> objects to be deleted
556     * @throws IdRepoException if there are repository related error conditions.
557     * @throws SSOException if user's single sign on token is invalid.
558     */
559    public void deleteIdentities(Set identities) throws IdRepoException,
560            SSOException {
561        if (identities == null || identities.isEmpty()) {
562            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.ILLEGAL_ARGUMENTS, null);
563        }
564
565        Iterator it = identities.iterator();
566        while (it.hasNext()) {
567            AMIdentity id = (AMIdentity) it.next();
568            IdServices idServices = IdServicesFactory.getDataStoreServices();
569            idServices.delete(token, id.getType(), id.getName(), organizationDN,
570                    id.getDN());
571        }
572    }
573
574    /**
575     * Non-javadoc, non-public methods Returns <code>true</code> if the data
576     * store has successfully authenticated the identity with the provided
577     * credentials. In case the data store requires additional credentials, the
578     * list would be returned via the <code>IdRepoException</code> exception.
579     *
580     * @param credentials
581     *            Array of callback objects containing information such as
582     *            username and password.
583     *
584     * @return <code>true</code> if data store authenticates the identity;
585     *         else <code>false</code>
586     */
587    public boolean authenticate(Callback[] credentials) throws IdRepoException,
588            com.sun.identity.authentication.spi.AuthLoginException {
589        IdServices idServices = IdServicesFactory.getDataStoreServices();
590        return (idServices.authenticate(organizationDN, credentials));
591    }
592
593    /**
594     * Non-javadoc, non-public methods Returns <code>true</code> if the data
595     * store has successfully authenticated the identity with the provided
596     * credentials. In case the data store requires additional credentials, the
597     * list would be returned via the <code>IdRepoException</code> exception.
598     *
599     * @param credentials
600     *            Array of callback objects containing information such as
601     *            username and password.
602     * @param idType
603     *            The type of identity to authenticate as, or null for any.
604     *
605     * @return <code>true</code> if data store authenticates the identity;
606     *         else <code>false</code>
607     */
608    public boolean authenticate(IdType idType, Callback[] credentials) throws IdRepoException,
609            com.sun.identity.authentication.spi.AuthLoginException {
610        IdServices idServices = IdServicesFactory.getDataStoreServices();
611        return idServices.authenticate(organizationDN, credentials, idType);
612    }
613
614    /**
615     * @supported.api
616     *
617     * Adds a listener, which should receive notifications for all changes that
618     * occurred in this organization.
619     *
620     * This method is only valid for IdType User and Agent.
621     *
622     * @param listener
623     *            The callback which implements <code>AMEventListener</code>.
624     * @return Integer identifier for this listener.
625     */
626    public int addEventListener(IdEventListener listener) {
627        ArrayList listOfListeners = (ArrayList) listeners.get(organizationDN);
628        if (listOfListeners == null) {
629            listOfListeners = new ArrayList();
630        }
631        synchronized (listeners) {
632            listOfListeners.add(listener);
633            listeners.put(organizationDN, listOfListeners);
634        }
635        return (listOfListeners.size() - 1);
636    }
637
638    /**
639     * @supported.api
640     *
641     * Removes listener as the application is no longer interested in receiving
642     * notifications.
643     *
644     * @param identifier
645     *            Integer identifying the listener.
646     */
647    public void removeEventListener(int identifier) {
648        ArrayList listOfListeners = (ArrayList) listeners.get(organizationDN);
649        if (listOfListeners != null) {
650            synchronized (listeners) {
651                listOfListeners.remove(identifier);
652            }
653        }
654    }
655
656    /**
657     * @supported.api
658     *
659     * Clears the cache.
660     */
661    public static void clearCache() {
662        IdServices idServices = IdServicesFactory.getDataStoreServices();
663        idServices.reinitialize();
664        IdUtils.initialize();
665    }
666
667
668    public IdSearchResults getSpecialIdentities(SSOToken token, IdType type,
669            String orgName) throws IdRepoException, SSOException {
670        IdServices idServices = IdServicesFactory.getDataStoreServices();
671        return idServices.getSpecialIdentities(token, type, orgName);
672    }
673
674    /**
675     * Return String representation of the <code>AMIdentityRepository
676     * </code> object. It returns realm name.
677     *
678     * @return String representation of <code>AMIdentityRepository</code>
679     * object.
680     */
681    public String toString() {
682        StringBuilder sb = new StringBuilder(100);
683        sb.append("AMIdentityRepository object: ")
684            .append(organizationDN);
685        return (sb.toString());
686    }
687
688    // TODO:
689    // FIXME: Move these utilities to a util class
690    private Map reverseMapAttributeNames(Map attrMap, Map configMap) {
691        if (attrMap == null || attrMap.isEmpty()) {
692            return attrMap;
693        }
694        Map resultMap;
695        Map[] mapArray = getAttributeNameMap(configMap);
696        if (mapArray == null) {
697            resultMap = attrMap;
698        } else {
699            resultMap = new CaseInsensitiveHashMap();
700            Map reverseMap = mapArray[1];
701            Iterator it = attrMap.keySet().iterator();
702            while (it.hasNext()) {
703                String curr = (String) it.next();
704                if (reverseMap.containsKey(curr)) {
705                    resultMap.put((String) reverseMap.get(curr), (Set) attrMap
706                            .get(curr));
707                } else {
708                    resultMap.put(curr, (Set) attrMap.get(curr));
709                }
710            }
711        }
712        return resultMap;
713    }
714
715    private IdSearchResults combineSearchResults(SSOToken token,
716            Object[][] arrayOfResult, int sizeOfArray, IdType type,
717            String orgName, boolean amsdkIncluded, Object[][] amsdkResults) {
718        Map amsdkDNs = new CaseInsensitiveHashMap();
719        Map resultsMap = new CaseInsensitiveHashMap();
720        int errorCode = IdSearchResults.SUCCESS;
721        if (amsdkIncluded) {
722            RepoSearchResults amsdkRepoRes = (RepoSearchResults)
723                amsdkResults[0][0];
724            Set results = amsdkRepoRes.getSearchResults();
725            Map attrResults = amsdkRepoRes.getResultAttributes();
726            for (String dn : (Set<String>) results) {
727                String name = LDAPUtils.rdnValueFromDn(dn);
728                amsdkDNs.put(name, dn);
729                Set attrMaps = new HashSet();
730                attrMaps.add((Map) attrResults.get(dn));
731                resultsMap.put(name, attrMaps);
732            }
733            errorCode = amsdkRepoRes.getErrorCode();
734        }
735        for (int i = 0; i < sizeOfArray; i++) {
736            RepoSearchResults current = (RepoSearchResults) arrayOfResult[i][0];
737            Map configMap = (Map) arrayOfResult[i][1];
738            Map allAttrMaps = current.getResultAttributes();
739            for (String m : (Set<String>) current.getSearchResults()) {
740                String mname = DNUtils.DNtoName(m);
741                Map attrMap = (Map) allAttrMaps.get(m);
742                attrMap = reverseMapAttributeNames(attrMap, configMap);
743                Set attrMaps = (Set) resultsMap.get(mname);
744                if (attrMaps == null) {
745                    attrMaps = new HashSet();
746                }
747                attrMaps.add(attrMap);
748                resultsMap.put(mname, attrMaps);
749            }
750        }
751        IdSearchResults results = new IdSearchResults(type, orgName);
752        for (String mname : (Set<String>) resultsMap.keySet()) {
753            Map combinedMap = combineAttrMaps((Set) resultsMap.get(mname),
754                    true);
755            AMIdentity id = new AMIdentity(token, mname, type, orgName,
756                    (String) amsdkDNs.get(mname));
757            results.addResult(id, combinedMap);
758        }
759        results.setErrorCode(errorCode);
760        return results;
761    }
762
763    private Map[] getAttributeNameMap(Map configMap) {
764        Set attributeMap = (Set) configMap.get(IdConstants.ATTR_MAP);
765
766        if (attributeMap == null || attributeMap.isEmpty()) {
767            return null;
768        } else {
769            Map returnArray[] = new Map[2];
770            int size = attributeMap.size();
771            returnArray[0] = new CaseInsensitiveHashMap(size);
772            returnArray[1] = new CaseInsensitiveHashMap(size);
773            Iterator it = attributeMap.iterator();
774            while (it.hasNext()) {
775                String mapString = (String) it.next();
776                int eqIndex = mapString.indexOf('=');
777                if (eqIndex > -1) {
778                    String first = mapString.substring(0, eqIndex);
779                    String second = mapString.substring(eqIndex + 1);
780                    returnArray[0].put(first, second);
781                    returnArray[1].put(second, first);
782                } else {
783                    returnArray[0].put(mapString, mapString);
784                    returnArray[1].put(mapString, mapString);
785                }
786            }
787            return returnArray;
788        }
789    }
790
791    private Map combineAttrMaps(Set setOfMaps, boolean isString) {
792        // Map resultMap = new CaseInsensitiveHashMap();
793        Map resultMap = new AMHashMap();
794        Iterator it = setOfMaps.iterator();
795        while (it.hasNext()) {
796            Map currMap = (Map) it.next();
797            if (currMap != null) {
798                Iterator keyset = currMap.keySet().iterator();
799                while (keyset.hasNext()) {
800                    String thisAttr = (String) keyset.next();
801                    if (isString) {
802                        Set resultSet = (Set) resultMap.get(thisAttr);
803                        Set thisSet = (Set) currMap.get(thisAttr);
804                        if (resultSet != null) {
805                            resultSet.addAll(thisSet);
806                        } else {
807                            /*
808                             * create a new Set so that we do not alter the set
809                             * that is referenced in setOfMaps
810                             */
811                            resultSet = new HashSet((Set)
812                                    currMap.get(thisAttr));
813                            resultMap.put(thisAttr, resultSet);
814                        }
815                    } else { // binary attributes
816
817                        byte[][] resultSet = (byte[][]) resultMap.get(thisAttr);
818                        byte[][] thisSet = (byte[][]) currMap.get(thisAttr);
819                        int combinedSize = thisSet.length;
820                        if (resultSet != null) {
821                            combinedSize = resultSet.length + thisSet.length;
822                            byte[][] tmpSet = new byte[combinedSize][];
823                            for (int i = 0; i < resultSet.length; i++) {
824                                tmpSet[i] = (byte[]) resultSet[i];
825                            }
826                            for (int i = 0; i < thisSet.length; i++) {
827                                tmpSet[i] = (byte[]) thisSet[i];
828                            }
829                            resultSet = tmpSet;
830                        } else {
831                            resultSet = (byte[][]) thisSet.clone();
832                        }
833                        resultMap.put(thisAttr, resultSet);
834
835                    }
836
837                }
838            }
839        }
840        return resultMap;
841    }
842}