/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: IdUtils.java,v 1.34 2009/11/20 23:52:54 ww203982 Exp $
 *
 * Portions Copyrighted 2011-2016 ForgeRock AS.
 * Portions Copyrighted 2014 Nomura Research Institute, Ltd
 */

package com.sun.identity.idm;

import java.security.AccessController;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import com.iplanet.am.sdk.AMConstants;
import com.iplanet.am.sdk.AMDirectoryAccessFactory;
import com.iplanet.am.sdk.AMException;
import com.iplanet.am.sdk.AMObject;
import com.iplanet.am.sdk.AMOrganization;
import com.iplanet.am.sdk.AMStoreConnection;
import com.iplanet.am.sdk.common.IDirectoryServices;
import com.iplanet.am.util.SystemProperties;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.service.AuthD;
import com.sun.identity.common.CaseInsensitiveHashMap;
import com.sun.identity.common.CaseInsensitiveHashSet;
import com.sun.identity.common.DNUtils;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.DNMapper;
import com.sun.identity.sm.OrgConfigViaAMSDK;
import com.sun.identity.sm.OrganizationConfigManager;
import com.sun.identity.sm.SMSEntry;
import com.sun.identity.sm.SMSException;
import com.sun.identity.sm.ServiceConfig;
import com.sun.identity.sm.ServiceConfigManager;
import com.sun.identity.sm.ServiceManager;
import org.forgerock.openam.ldap.LDAPUtils;
import org.forgerock.openam.utils.CollectionUtils;
import org.forgerock.openam.utils.StringUtils;
import org.forgerock.opendj.ldap.DN;

/**
 * The class defines some static utilities used by other components like policy
 * and auth
 *
 * @supported.api
 */
public final class IdUtils {
    private static Debug debug = AMIdentityRepository.debug;

    private static Map mapSupportedTypes = new CaseInsensitiveHashMap(10);

    public static Set supportedTypes = new HashSet();

    private static Map mapTypesToServiceNames = new CaseInsensitiveHashMap();

    protected static Map typesCanBeMemberOf = new CaseInsensitiveHashMap();

    protected static Map typesCanHaveMembers = new CaseInsensitiveHashMap();

    protected static Map typesCanAddMembers = new CaseInsensitiveHashMap();

    // Static map to cache "orgIdentifier" and organization DN
    private static Map<String, String> orgIdentifierToOrgName = Collections.synchronizedMap(
        new CaseInsensitiveHashMap<String, String>());
    private static Map<String, Boolean> orgStatusCache = Collections.synchronizedMap(
        new CaseInsensitiveHashMap<String, Boolean>());

    // Static Set to cache unknown organisation lookups
    private static Set<String> unknownOrgLookupCache =
            Collections.synchronizedSet(new CaseInsensitiveHashSet<String>());

    // ServiceConfigManager for sunidentityrepository service
    private static String notificationId;

    private static ServiceConfigManager serviceConfigManager;
    
    // User naming attribute for AMSDK
    private static String USER_NAMING_ATTR;
    
    // Organization naming attribute for AMSDK
    private static String ORG_NAMING_ATTR;
    
    // SMS Root Suffix
    private static String ROOT_SUFFIX;
    
    // DN pointing to the services node
    private static String SERVICES_SUFFIX;
    
    // Special Users
    private static Set specialUsers = new HashSet();
    
    static {
        initialize();
    }

    protected static void initialize() {
        if (ServiceManager.isConfigMigratedTo70()) {
            // IdRepo service schema exists. Read the supported
            // entities from there
            try {
                SSOToken adminToken = (SSOToken) AccessController
                        .doPrivileged(AdminTokenAction.getInstance());
                serviceConfigManager = new ServiceConfigManager(adminToken,
                        IdConstants.REPO_SERVICE, "1.0");
                ServiceConfig ss = serviceConfigManager.getGlobalConfig(null);
                Set typeSchemaNames = ss.getSubConfigNames("*",
                        IdConstants.SUPPORTED_TYPES);
                if (typeSchemaNames == null || typeSchemaNames.isEmpty()) {
                    loadDefaultTypes();
                } else {
                    Iterator it = typeSchemaNames.iterator();
                    while (it.hasNext()) {
                        String typeSchema = (String) it.next();
                        IdType idType = new IdType(typeSchema);
                        supportedTypes.add(idType);
                        mapSupportedTypes.put(idType.getName(), idType);
                        ServiceConfig tsc = ss.getSubConfig(typeSchema);
                        Map attributes = tsc.getAttributes();
                        Set serviceNameSet = (Set) attributes
                                .get(IdConstants.SERVICE_NAME);
                        Set canBeMembersOf = (Set) attributes
                                .get(IdConstants.ATTR_MEMBER_OF);
                        Set canHaveMembers = (Set) attributes
                                .get(IdConstants.ATTR_HAVE_MEMBERS);
                        Set canAddMembers = (Set) attributes
                                .get(IdConstants.ATTR_ADD_MEMBERS);
                        if (serviceNameSet != null && 
                                !serviceNameSet.isEmpty()) {
                            mapTypesToServiceNames.put(typeSchema,
                                    (String) serviceNameSet.iterator().next());
                        }
                        if (canBeMembersOf != null && 
                                !canBeMembersOf.isEmpty()) {
                            Set memberOfSet = getMemberSet(canBeMembersOf);
                            typesCanBeMemberOf.put(typeSchema, memberOfSet);
                        }
                        if (canHaveMembers != null && 
                                !canHaveMembers.isEmpty()) {
                            Set memberSet = getMemberSet(canHaveMembers);
                            typesCanHaveMembers.put(typeSchema, memberSet);
                        }
                        if (canAddMembers != null && 
                                !canAddMembers.isEmpty()) 
                        {
                            Set memberSet = getMemberSet(canAddMembers);
                            typesCanAddMembers.put(typeSchema, memberSet);
                        }
                    }
                }
            } catch (SMSException e) {
                String installTime = SystemProperties.get(
                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
                if (!installTime.equals("true")) {
                    debug.error(
                        "IdUtils.initialize: Loading default types.", e);
                }
                loadDefaultTypes();
            } catch  (SSOException ssoe) {
                debug.error("dUtils.initialize: Loading default types", ssoe);
                loadDefaultTypes();
            }
        } else {
            loadDefaultTypes();
        }

        // Register for SMS notifications to root realm
        if (notificationId == null) {
            try {
                SSOToken adminToken = (SSOToken) AccessController
                        .doPrivileged(AdminTokenAction.getInstance());
                if (serviceConfigManager == null) {
                    serviceConfigManager = new ServiceConfigManager(adminToken,
                        IdConstants.REPO_SERVICE, "1.0");
                }
                notificationId = serviceConfigManager.addListener(
                    new IdUtilsListener());
            } catch (SMSException e) {
                String installTime = SystemProperties.get(
                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
                if (!installTime.equals("true")) {
                    debug.error(
                        "IdUtils.initialize: Register notification", e);
                }
            } catch (SSOException ssoe) {
                String installTime = SystemProperties.get(
                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
                if (!installTime.equals("true")) {
                    debug.error(
                        "IdUtils.initialize: Register notification", ssoe);
                }
            }
        }
    }

    /**
     * @supported.api 
     * Returns a handle of the Identity object based on
     * the SSO Token passed in (<code>AMIdentity</code> object of the user
     * who is authenticated).
     * 
     * @param token    Single sign on token of user.
     * @return Identity object.
     * @throws IdRepoException if there are repository related error conditions.
     * @throws SSOException if user's single sign on token is invalid.
     */
    public static AMIdentity getIdentity(SSOToken token)
        throws IdRepoException, SSOException {
        String principal = token.getProperty(Constants.UNIVERSAL_IDENTIFIER);
        if (principal == null) {
            // This could happen during co-existence with AM 6.x
            // and SSOToken created by AM 6.x server. In this case
            // the principal name would be the DN
            principal = token.getPrincipal().getName();
        }
        return (getIdentity(token, principal));
    }

    /**
     * @supported.api
     * 
     * Returns a string which uniquely represents this identity object.
     * 
     * @param id
     *            <code>AMIdentity</code> object whose string represenation is
     *            needed.
     * @return universal identifier of <code>id</code>.
     */
    public static String getUniversalId(AMIdentity id) {
        return id.getUniversalId();
    }

    /**
     * @supported.api 
     * 
     * Returns an <code>AMIdentity</code> object, if provided with a string 
     * identifier for the object.
     * 
     * @param token SSOToken of the administrator
     * @param univId String represenation of the identity.
     * @return Identity object
     * @throws IdRepoException if the identifier provided is wrong.
     */
    public static AMIdentity getIdentity(SSOToken token, String univId)
            throws IdRepoException {
        return (getIdentity(token, univId, null));
    }

    /**
     * Returns an <code>AMIdentity</code> object, given the
     * DN of an authenticated identity, realm name and identity type.
     * This interface is mainly for authentication component to get
     * back the identity of the user.
     * 
     * @param token SSOToken of the administrator
     * @param amsdkdn DN of the authenticated user
     * @param realm  realm name where the user was authenticated
     * @return Identity object or <code>null</code> 
     * @throws IdRepoException if the underly components throws
     * exception while obtaining the identity object
     */
    public static AMIdentity getIdentity(SSOToken token, String amsdkdn,
        String realm) throws IdRepoException {
        if (amsdkdn == null || !LDAPUtils.isDN(amsdkdn)) {
            Object[] args = { amsdkdn };
            throw (new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.ILLEGAL_UNIVERSAL_IDENTIFIER, args));
        }
        DN amsdkdnObject = LDAPUtils.newDN(amsdkdn);

        // Try constructing the identity object
        if (amsdkdn.toLowerCase().startsWith("id=")) {
            try {
                return (new AMIdentity(amsdkdnObject, token));
            } catch (IdRepoException ide) {
                // this could be a AMSDK DN. Follow the AMSDK rules
                if (debug.messageEnabled()) {
                    debug.message("IdUtils:getIdentity(token, " +
                        amsdkdn + ") got exception: " + ide.getMessage() +
                        "\n\tContinuing with AMSDK DN check");
                }
            }
        }

        // Check for Special Users
        initializeSpecialUsers();
        if (specialUsers.contains(DNUtils.normalizeDN(amsdkdn))) {
            return new AMIdentity(amsdkdnObject, token, LDAPUtils.rdnValueFromDn(
                    amsdkdnObject), IdType.USER, ROOT_SUFFIX);
        }

        // Since "amsdkdn" is not a UUID, check if realm has AMSDK configured
        // This change is to avoid the issue of IdUtils always checking the 
        // users in AMSDK as IdUtils does not check if AMSDK is configured in 
        // any of the realms. 
        try {
            if (!ServiceManager.isAMSDKEnabled() || ((realm != null) && 
                !OrgConfigViaAMSDK.isAMSDKConfigured(realm)) ||
                    (!ServiceManager.isAMSDKConfigured())) { 
                // Not configured for AMSDK, return
                return (null);
            }
        } catch (SMSException smse) {
            // Ignore the exception and continue
        } 

        // Initialize root realm suffix, org and user naming attributes
        initializeForGetIdentity();

        // Determine if the amsdkdn is valid. Obtain name & type
        String name = null;
        IdType type = null;
        try {
            // Since we would using AMSDK, get AMDirectoryManager preload
            // all the attributes and check if it exists
            IDirectoryServices dsServices = 
                AMDirectoryAccessFactory.getDirectoryServices();
            // Preload/cache all the attributes assuming it is a user
            // Mainly for performance reasons, since getObjectType would
            // force multiple another directory lookup
            try {
                if (amsdkdn.startsWith(USER_NAMING_ATTR)) {
                    dsServices.getAttributes(token, amsdkdn, AMObject.USER);
                }
            } catch (Exception e) {
                // Ignore the exception and continue since this for cache
            }

            // Getting object type would use the cached attributes
            int sdkType = dsServices.getObjectType(token, amsdkdn);

            // Convert the sdkType to IdRepo type
            type = getType(AMStoreConnection.getObjectName(sdkType));
            name = AMConstants.CONTAINER_DEFAULT_TEMPLATE_ROLE;
            if (!type.equals(IdType.REALM)) {
                name = LDAPUtils.rdnValueFromDn(amsdkdnObject);
            }
        } catch (AMException ame) {
            // Debug the message and return null
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getIdentity: Unable to resolve " +
                    "AMSDK DN: " + amsdkdn, ame);
            }
            return (null);
        } catch (SSOException ssoe) {
            // Debug the message and return null
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getIdentity: Unable to resolve " +
                    "AMSDK DN. Got SSOException", ssoe);
            }
            return (null);
        }

        // Need to determine realm for amsdkdn
        String srealm = ROOT_SUFFIX;
        if (!amsdkdn.equals(ROOT_SUFFIX) &&
            !amsdkdn.equals(SERVICES_SUFFIX)) {
            // Need to get the object type and walk up the tree
            int index = amsdkdn.indexOf(ORG_NAMING_ATTR);
            if (index == 0) {
                srealm = OrgConfigViaAMSDK.getRealmForAMSDK(amsdkdn, realm);
            } else if (index > 0) {
                srealm = OrgConfigViaAMSDK.getRealmForAMSDK(
                    amsdkdn.substring(index), realm);
            }
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getIdentity:: amsdkdn=" +
                    amsdkdn + " maps to realm=" + srealm);
            }
        } else if (amsdkdn.equals(SERVICES_SUFFIX)) {
            // Since amsdkdn points to services node,
            // it should be reset to root suffix
            amsdkdn = ROOT_SUFFIX;
        }

        return (new AMIdentity(amsdkdnObject, token, name, type, srealm));
    }

    /**
     * Returns the name of service which defines the profile information for
     * this type. Returns null, if nothing is defined.
     * 
     * @param type IdType whose service name is needed.
     * @return Name of the service.
     */
    public static String getServiceName(IdType type) {
        return (String) mapTypesToServiceNames.get(type.getName());
    }

    /**
     * Returns corresponding <code>IdType</code> object given a type.
     * 
     * @param type of object to return.
     * @return Idtype of type.
     * @throws IdRepoException if there are no corresponding types.
     */
    public static IdType getType(String type) throws IdRepoException {
        if (type.equalsIgnoreCase("managedrole")) {
            type = "role";
        } else if (type.equalsIgnoreCase("organization")
                || type.equalsIgnoreCase("organizationalunit")) {
            type = "realm";
        }

        IdType returnType = (IdType) mapSupportedTypes.get(type);
        if (returnType == null) {
            Object args[] = { type };
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                    IdRepoErrorCode.NOT_SUPPORTED_TYPE, args);
        }
        return returnType;
    }

    /**
     * Returns the matching DN from the AM SDK for this entry. This utility is
     * required by auth.
     * 
     * @param id  <code>AMIdentity</code> object.
     * @return <code>DN</code> of the object, as represented in the datastore.
     */
    public static String getDN(AMIdentity id) {
        if (id.getDN() != null) {
            return id.getDN();
        } else {
            return id.getUniversalId();
        }
    }

    /**
     * Returns an organization which maps to the identifier used by application
     * 
     * @param orgIdentifier  Organization identifier
     * @return Organization mapping to that identifier.
     */
    public static String getOrganization(SSOToken token, String orgIdentifier)
            throws IdRepoException, SSOException {
        // Check in cache first
        String id = orgIdentifierToOrgName.get(orgIdentifier);
        if (id != null) {
            return (id);
        }

        String[] args = {orgIdentifier};
        // Don't go to the expense of an organisation search if we have failed to look this up already.
        if (unknownOrgLookupCache.contains(orgIdentifier)) {
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getOrganization: orgIdentifier {} found in unknown org lookup cache.",
                        orgIdentifier);
            }
            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
        }

        // Compute the organization name
        if (debug.messageEnabled()) {
            debug.message("IdUtils:getOrganization Input orgname: "
                    + orgIdentifier);
        }
        if (orgIdentifier == null || orgIdentifier.length() == 0
                || orgIdentifier.equals("/")) {
            // Return base DN
            id = DNMapper.orgNameToDN("/");
        } else if (orgIdentifier.startsWith("/")) {
            // If orgIdentifier is in "/" format covert to DN and return
            id = DNMapper.orgNameToDN(orgIdentifier);
            try {
                new OrganizationConfigManager(token, orgIdentifier);
            } catch (SMSException e) {
                if (debug.messageEnabled()) {
                    debug.message("IdUtils.getOrganization Exception in getting org name from SMS", e);
                }
                unknownOrgLookupCache.add(orgIdentifier);
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
            }
        } else if (LDAPUtils.isDN(orgIdentifier)) {
            id = orgIdentifier;
            try {
                // Search for realms with orgIdentifier name
                OrganizationConfigManager ocm = 
                    new OrganizationConfigManager(token, orgIdentifier);
            } catch (SMSException smse) {
                if (debug.messageEnabled()) {
                    debug.message("IdUtils.getOrganization Exception in getting org name from SMS", smse);
                }
                unknownOrgLookupCache.add(orgIdentifier);
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
            }
        } else if (ServiceManager.isCoexistenceMode()) {
            // Return the org DN as determined by AMStoreConnection
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getOrganization: getting from AMSDK");
            }
            try {
                AMStoreConnection amsc = new AMStoreConnection(token);
                id = amsc.getOrganizationDN(orgIdentifier, null);
            } catch (AMException ame) {
                if (debug.messageEnabled()) {
                    debug.message("IdUtils.getOrganization Exception in "
                            + "getting org name from AMSDK", ame);
                }
                throw convertAMException(ame);
            }
        } else {
            // Get the realm name from SMS
            if (debug.messageEnabled()) {
                debug.message("IdUtils.getOrganization: getting from " +
                    "SMS realms");
            }
            try {
                boolean foundOrg = false;
                ServiceManager sm = new ServiceManager(token);
                // First search for realms with orgIdentifier name
                OrganizationConfigManager ocm = sm.getOrganizationConfigManager("/");
                Set<String> subOrgNames = ocm.getSubOrganizationNames(orgIdentifier, true);
                if (CollectionUtils.isNotEmpty(subOrgNames)) {
                    if (subOrgNames.size() == 1) {
                        id = DNMapper.orgNameToDN(subOrgNames.iterator().next());
                        foundOrg = true;
                    } else {
                        for (String subRealmName : subOrgNames) {
                            // check for orgIdentifier
                            StringTokenizer st = new StringTokenizer(subRealmName, "/");
                            // Need to handle the scenario where multiple
                            // sub-realm with the same name should not be
                            // allowed
                            while (st.hasMoreTokens()) {
                                if (st.nextToken().equalsIgnoreCase(orgIdentifier)) {
                                    if (!foundOrg) {
                                        id = DNMapper.orgNameToDN(subRealmName);
                                        foundOrg = true;
                                    } else {
                                        throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                                                IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
                                    }
                                }
                            }
                        }
                    }
                }

                // Check if organization name has been determined
                if (debug.messageEnabled()) {
                    debug.message("IdUtils.getOrganization: getting from " +
                        "SMS realms aliases");
                }
                // perform organization alias search
                Set<String> vals = new HashSet<>(1);
                vals.add(orgIdentifier);
                Set orgAliases = sm.searchOrganizationNames(
                        IdConstants.REPO_SERVICE,
                        IdConstants.ORGANIZATION_ALIAS_ATTR, vals);
                if (!foundOrg && CollectionUtils.isEmpty(orgAliases)) {
                    if (debug.warningEnabled()) {
                        debug.warning("IdUtils.getOrganization Unable to find Org name for: " + orgIdentifier);
                    }
                    unknownOrgLookupCache.add(orgIdentifier);
                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                            IdRepoErrorCode.NO_MAPPING_FOUND, args);
                } else if (CollectionUtils.isNotEmpty(orgAliases) && foundOrg) {
                    // Check to see if there is one alias, which points to the same realm as the one previously found
                    boolean sameRealm = false;
                    if (orgAliases.size() == 1) {
                        String aliasId = DNMapper.orgNameToDN((String)orgAliases.iterator().next());
                        if (StringUtils.isEqualTo(aliasId, id)) {
                            // The realm has an alias that is equivalent to the realm name
                            sameRealm = true;
                        }
                    }
                    if (!sameRealm) {
                        // Multiple realms should not have the same alias
                        if (debug.warningEnabled()) {
                            debug.warning("IdUtils.getOrganization Multiple " +
                                    " matching Orgs found for: " + orgIdentifier);
                        }
                        throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                                IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
                    }
                } else if (CollectionUtils.isNotEmpty(orgAliases) && (orgAliases.size() > 1)) {
                    // Multiple realms should not have the same alias
                    if (debug.warningEnabled()) {
                        debug.warning("IdUtils.getOrganization Multiple " +
                                " matching Orgs found for: " + orgIdentifier);
                    }
                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
                            IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
                }
                if (!foundOrg) {
                    String tmpS = (String) orgAliases.iterator().next();
                    id = DNMapper.orgNameToDN(tmpS);
                }
            } catch (SMSException smse) {
                if (debug.messageEnabled()) {
                    debug.message("IdUtils.getOrganization Exception in "
                            + "getting org name from SMS", smse);
                }
                unknownOrgLookupCache.add(orgIdentifier);
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
            }
        }

        if (debug.messageEnabled()) {
            debug.message("IdUtils:getOrganization Search for OrgIdentifier:" +
                orgIdentifier + " returning realm DN: " + id);
        }

        // Add to cache and return id
        orgIdentifierToOrgName.put(orgIdentifier, id);
        return id;
    }

    /**
     * Clears the cache containing orgIdentifiers to organization names
     */
    protected static void clearOrganizationNamesCache() {
        orgIdentifierToOrgName.clear();
        orgStatusCache.clear();
        unknownOrgLookupCache.clear();
        if (debug.messageEnabled()) {
            debug.message("IdUtils.clearOrganizationNamesCache called");
        }
    }

    /**
     * Returs true or false, depending on if this organization is enabled or
     * not. The organization string passed to this method should be an
     * identifier returned from the method
     * <code> IdUtils.getOrganization </code>. In the default mode, where
     * relams are enabled but backward comaptibility is required, this checks
     * for organization status in the AM enabled Sun DS. Otherwise, it checks
     * for organization status from the realms tree.
     * 
     * @param token token SSOToken a valid SSOToken.
     * @param org name of the organization of interest.
     * @return <code>true</code> if org is active; 
     *    otherwise <code>false</code>
     * @throws IdRepoException if there are repository related error conditions.
     * @throws SSOException If user's single sign on token is invalid.
     */
    public static boolean isOrganizationActive(SSOToken token, String org)
            throws IdRepoException, SSOException {
        // Check the cache
        if (orgStatusCache.containsKey(org)) {
            return orgStatusCache.get(org);
        }
        boolean isActive = true;
        // Need to initialize ServiceManager by creating the constructor
        if (!ServiceManager.isCoexistenceMode()) {
            // Pick it up from the realms tree.
            try {
                OrganizationConfigManager ocm = new OrganizationConfigManager(
                        token, org);
                if (ocm == null) {
                    Object[] args = { org };
                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
                }
                Map attributes = ocm.getAttributes(IdConstants.REPO_SERVICE);
                Set vals = (Set) attributes
                        .get(IdConstants.ORGANIZATION_STATUS_ATTR);
                if (vals == null || vals.isEmpty()) {
                    isActive = true;
                } else {
                    String stringActive = (String) vals.iterator().next();
                    isActive = stringActive.equalsIgnoreCase("Active");
                }
            } catch (SMSException smse) {
                Object args[] = { org };
                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
            }
        } else if (ServiceManager.isAMSDKEnabled()) {
            // Return the org DN as determined by AMStoreConnection.
            try {
                AMStoreConnection amsc = new AMStoreConnection(token);
                AMOrganization orgObj = amsc.getOrganization(org);
                isActive = orgObj.isActivated();
            } catch (AMException ame) {
                throw convertAMException(ame);
            }
        }
        // Add to cache
        orgStatusCache.put(org, isActive);
        return isActive;
    }

    private static void initializeForGetIdentity() {
        // Initialize root realm, if not already initalized
        if (ROOT_SUFFIX == null) {
            ROOT_SUFFIX = SMSEntry.getRootSuffix();
            StringBuilder sb = new StringBuilder(100);
            sb.append(SMSEntry.SERVICES_RDN)
                .append(SMSEntry.COMMA).append(ROOT_SUFFIX);
            SERVICES_SUFFIX = DNUtils.normalizeDN(sb.toString());
        }

        // Initialize organization and user naming attributes
        if ((ORG_NAMING_ATTR == null) || (USER_NAMING_ATTR == null)) {
            try {
                ORG_NAMING_ATTR = AMStoreConnection.getNamingAttribute(
                    AMObject.ORGANIZATION).toLowerCase() + "=";
                USER_NAMING_ATTR = AMStoreConnection.getNamingAttribute(
                    AMObject.USER).toLowerCase() + "=";
            } catch (AMException ame) {
                if (debug.warningEnabled()) {
                    debug.warning("IdUtils: unable to get naming " +
                        "attribute for org/user. Using \"o\"/\"uid\"");
                }
                ORG_NAMING_ATTR = "o=";
                USER_NAMING_ATTR = "uid=";
            }
        }
    }

    private static void initializeSpecialUsers() {
        // Populate special users
        if (specialUsers.isEmpty()) {
            String susers = SystemProperties.get(
                Constants.AUTHENTICATION_SPECIAL_USERS, "");
            StringTokenizer st = new StringTokenizer(
                susers, "|");
            while (st.hasMoreTokens()) {
                specialUsers.add(DNUtils.normalizeDN(st.nextToken()));
            }
            susers = SystemProperties.get(
                "com.sun.identity.authentication.super.user", "");
            specialUsers.add(DNUtils.normalizeDN(susers));
        }
    }
    
    /**
     * Returns an IdRepoException based on an <code>AMException</code>
     * 
     * @param ame
     * @return IdRepoException based on ame.
     */
    public static IdRepoException convertAMException(AMException ame) {
        Object[] args = ame.getMessageArgs();
        String eCode = ame.getErrorCode();
        IdRepoException ide = null;
        if (args == null) {
            ide = new IdRepoException("amProfile", eCode, null);
        } else { 
            ide = new IdRepoException("amProfile", ame.getErrorCode(), args);
        }
        ide.setLDAPErrorCode(ame.getLDAPErrorCode());
        return ide;
    }

    private static void loadDefaultTypes() {
        supportedTypes.add(IdType.REALM);
        supportedTypes.add(IdType.AGENT);
        supportedTypes.add(IdType.USER);
        supportedTypes.add(IdType.ROLE);
        supportedTypes.add(IdType.GROUP);
        supportedTypes.add(IdType.FILTEREDROLE);
        mapSupportedTypes.put(IdType.REALM.getName(), IdType.REALM);        
        mapSupportedTypes.put(IdType.USER.getName(), IdType.USER);
        mapSupportedTypes.put(IdType.ROLE.getName(), IdType.ROLE);
        mapSupportedTypes.put(IdType.FILTEREDROLE.getName(),
                IdType.FILTEREDROLE);
        mapSupportedTypes.put(IdType.AGENT.getName(), IdType.AGENT);
        mapSupportedTypes.put(IdType.GROUP.getName(), IdType.GROUP);
        Set memberSet = new HashSet();
        memberSet.add(IdType.ROLE);
        memberSet.add(IdType.GROUP);
        memberSet.add(IdType.FILTEREDROLE);
        typesCanBeMemberOf.put(IdType.USER.getName(), memberSet);
        Set memberShipSet = new HashSet();
        memberShipSet.add(IdType.USER);
        typesCanHaveMembers.put(IdType.ROLE.getName(), memberShipSet);
        typesCanHaveMembers.put(IdType.GROUP.getName(), memberShipSet);
        typesCanHaveMembers.put(IdType.FILTEREDROLE.getName(), memberShipSet);
        typesCanAddMembers.put(IdType.GROUP.getName(), memberShipSet);
        typesCanAddMembers.put(IdType.ROLE.getName(), memberShipSet);
    }

    private static Set getMemberSet(Set members) {
        Set memberSet = new HashSet(members.size() * 2);
        for (Iterator iter = members.iterator(); iter.hasNext();) {
            String currType = (String) iter.next();
            memberSet.add(new IdType(currType));
        }
        return memberSet;
    }

    /**
     * Returns the user name extracted from the uuid
     * if the orgName supplied in the parameter is 
     * not same realm name in uuid then <code>IdRepoException</code>
     * is thrown
     * 
     * @param uuid uuid of the user
     * @param orgName the org user is trying to login to
     * @return user name
     * @throws IdRepoException 
     */
    public static String getIdentityName(String uuid, String orgName)
        throws IdRepoException {
        String username = uuid;
        // Check uuid
        if ((uuid != null) && uuid.toLowerCase().startsWith("id=")) {
            // Could be universal id, get the identity object
            AMIdentity id = new AMIdentity(null, uuid);
            username = id.getName();
            // Check the realm names
            String realm = DNUtils.normalizeDN(id.getRealm());
            if (!DNUtils.normalizeDN(orgName).equals(realm)) {
                Object[] args = {uuid, orgName};
		throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.REALM_NAME_NOT_MATCH_AUTHENTICATION_REALM,
                args);
            }
        }
        return (username);
    }

    /**
     * Gets the AMIdentity of a user with username equal to uName that exists in realm
     *
     * @param uName username of the user to get.
     * @param realm realm the user belongs to.
     * @return The AMIdentity of user with username equal to uName.
     */
    public static AMIdentity getIdentity(String uName, String realm) {
        AMIdentity theID = null;

        AMIdentityRepository amIdRepo = getAMIdentityRepository(DNMapper.orgNameToDN(realm));

        IdSearchControl idsc = new IdSearchControl();
        idsc.setRecursive(true);
        idsc.setAllReturnAttributes(true);
        // search for the identity
        Set<AMIdentity> results = Collections.EMPTY_SET;
        try {
            idsc.setMaxResults(0);
            IdSearchResults searchResults = amIdRepo.searchIdentities(IdType.USER, uName, idsc);
            if (searchResults != null) {
                results = searchResults.getSearchResults();
            }

            if (results == null || results.size() != 1) {
                throw new IdRepoException("IdUtils" +
                        ".getIdentity : " +
                        "More than one user found");
            }
            theID = results.iterator().next();
        } catch (IdRepoException e) {
            debug.warning("Error searching for user identity");
        } catch (SSOException e) {
            debug.warning("User's ssoToken has expired");
        }
        return theID;
    }

    /**
     * Returns <code>AMIdentityRepostiory</code> handle for an organization.
     *
     * @param orgDN the organization name.
     * @return <code>AMIdentityRepostiory</code> object
     */
    public static AMIdentityRepository getAMIdentityRepository(String orgDN) {
        return AuthD.getAuth().getAMIdentityRepository(orgDN);
    }
    
    // SMS service listener to reinitialize if IdRepo service changes
    static class IdUtilsListener implements com.sun.identity.sm.ServiceListener 
    {
        public void schemaChanged(String serviceName, String version) {
            initialize();
        }

        public void globalConfigChanged(String serviceName, String version,
            String groupName, String serviceComponent, int type) {
            initialize();
            clearOrganizationNamesCache();
        }

        public void organizationConfigChanged(String serviceName,
            String version, String orgName, String groupName,
            String serviceComponent, int type) {
            clearOrganizationNamesCache();
        }
    }

}
