/*
 * 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: OrgConfigViaAMSDK.java,v 1.14 2009/11/20 23:52:56 ww203982 Exp $
 *
 * Portions Copyrighted 2011-2015 ForgeRock AS.
 */
package com.sun.identity.sm;

import static org.forgerock.openam.ldap.LDAPUtils.*;

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

import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.RDN;

import com.iplanet.am.sdk.AMConstants;
import com.iplanet.am.sdk.AMException;
import com.iplanet.am.sdk.AMNamingAttrManager;
import com.iplanet.am.sdk.AMObject;
import com.iplanet.am.sdk.AMOrganization;
import com.iplanet.am.sdk.AMSDKBundle;
import com.iplanet.am.sdk.AMStoreConnection;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.common.CaseInsensitiveHashMap;
import com.sun.identity.common.DNUtils;
import com.sun.identity.delegation.DelegationEvaluator;
import com.sun.identity.delegation.DelegationEvaluatorImpl;
import com.sun.identity.delegation.DelegationException;
import com.sun.identity.delegation.DelegationPermission;
import com.sun.identity.idm.IdConstants;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.debug.Debug;

// This class provides support for OrganizationConfigManager
// in coexistence mode. This class interfaces with AMSDK
// to manage organization names and organization attributes.
public class OrgConfigViaAMSDK {

    // Instance variables
    private SSOToken token;

    private String parentOrgName;

    private String smsOrgName;

    private AMOrganization parentOrg;

    private AMOrganization parentOrgWithAdminToken;

    private ServiceConfig serviceConfig;
    
    private int objType;

    // permissions for the user token
    boolean hasReadPermissionOnly;

    // Cache of organization names to ServiceConfig that
    // contains the attribute mappings
    static Map attributeMappings = new CaseInsensitiveHashMap();

    static Map reverseAttributeMappings = new CaseInsensitiveHashMap();

    static Map attributeMappingServiceConfigs = new HashMap();

    static final String IDREPO_SERVICE = "sunidentityrepositoryservice";

    static final String MAPPING_ATTR_NAME = "sunCoexistenceAttributeMapping";
    
    // Cache of AMSDK organization names to SMS relam dn
    static Map amsdkdn2realmname = new CaseInsensitiveHashMap();
    
    static Map amsdkConfiguredRealms = new CaseInsensitiveHashMap();

    // Debug & Locale
    static Debug debug = SMSEntry.debug;

    ResourceBundle bundle = SMSEntry.bundle;

    // When DIT not migrated to AM 7.0 we need to use static mapping
    static Map notMigratedAttributeMappings;

    static Map notMigratedReverseAttributeMappings;
    
    static {
        if (!ServiceManager.isConfigMigratedTo70()) {
            notMigratedAttributeMappings = new CaseInsensitiveHashMap();
            notMigratedAttributeMappings.put("sunPreferredDomain",
                    "sunPreferredDomain");
            notMigratedAttributeMappings.put("sunOrganizationStatus",
                    "inetDomainStatus");
            notMigratedAttributeMappings.put("sunOrganizationAliases",
                    "sunOrganizationAlias");
            notMigratedAttributeMappings.put("sunDNSAliases",
                    "associatedDomain");
            notMigratedReverseAttributeMappings = new CaseInsensitiveHashMap();
            notMigratedReverseAttributeMappings.put("sunPreferredDomain",
                    "sunPreferredDomain");
            notMigratedReverseAttributeMappings.put("inetDomainStatus",
                    "sunOrganizationStatus");
            notMigratedReverseAttributeMappings.put("sunOrganizationAlias",
                    "sunOrganizationAliases");
            notMigratedReverseAttributeMappings.put("associatedDomain",
                    "sunDNSAliases");
        }
    }

    /**
     * Constructor for Realm management via AMSDK The parameter
     * <code>orgName</code> must be LDAP organization name
     */
    OrgConfigViaAMSDK(SSOToken token, String orgName, String smsOrgName)
            throws SMSException {
        this.token = token;
        parentOrgName = orgName;
        this.smsOrgName = smsOrgName;
        
        // Get admin SSOToken for operations to bypass ACIs and delegation
        SSOToken adminToken = (SSOToken) AccessController
            .doPrivileged(AdminTokenAction.getInstance());

        try {
            // Check if the user has realm privileges, if yes use
            // admin SSOToken to bypass directory ACIs.
            // Look if the incoming request is from client or server.
            // If client,(SMSJAXRPCObjectFlg=true), and since it is a JAXRPC
            // call, the permission checking would be done at the server.
            // So client need not have this check.(checkRealmPermission)
            if (!SMSEntry.SMSJAXRPCObjectFlg) {
                if (checkRealmPermission(token, smsOrgName,
                    SMSEntry.modifyActionSet)) {
                    token = adminToken;
                } else if (checkRealmPermission(token, smsOrgName,
                    SMSEntry.readActionSet)) {
                    hasReadPermissionOnly = true;
                }
            }
            AMStoreConnection amcom = new AMStoreConnection(token);
            parentOrg = amcom.getOrganization(orgName);

            if (hasReadPermissionOnly) {
                // Construct parent org with admin token for reads
                amcom = new AMStoreConnection(adminToken);
                parentOrgWithAdminToken = amcom.getOrganization(orgName);
            }

            // Get the Realm <---> LDAP Org attribute mappings.
            // To get the service config of idrepo service.
            String newOrg = orgName;
            if (!SMSEntry.getRootSuffix().equalsIgnoreCase(
                SMSEntry.getAMSdkBaseDN())) {
                newOrg = smsOrgName;
            }

            if (ServiceManager.isConfigMigratedTo70() &&
                (serviceConfig = (ServiceConfig) attributeMappingServiceConfigs
                .get(orgName)) == null) {
                ServiceConfigManager scm = new ServiceConfigManager(
                    IDREPO_SERVICE, adminToken);
                // Do we need to use internal token?
                serviceConfig = scm.getOrganizationConfig(newOrg, null);
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK::constructor"
                        + ": serviceConfig" + serviceConfig);
                }
                attributeMappingServiceConfigs.put(orgName, serviceConfig);
            }
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Create a suborganization using AMSDK. The code checks if the DIT has been
     * migrated to AM 7.0 to add the objectclass "sunRelamService".
     */
    void createSubOrganization(String subOrgName) throws SMSException {
        // Check if suborg exists
        if (!getSubOrganizationNames(subOrgName, false).isEmpty()
                || subOrgName.startsWith(SMSEntry.SUN_INTERNAL_REALM_NAME)) {
            // Sub-org already exists or it is a hidden realm
            return;
        }

        // Create the organization
        try {
            if (ServiceManager.isConfigMigratedTo70()) {
                Map attrs = new HashMap();
                Set attrValues = new HashSet();
                attrValues.add(SMSEntry.OC_REALM_SERVICE);
                attrs.put(SMSEntry.ATTR_OBJECTCLASS, attrValues);
                Map subOrgs = new HashMap();
                subOrgs.put(subOrgName, attrs);
                parentOrg.createSubOrganizations(subOrgs);
            } else {
                Set subOrgs = new HashSet();
                subOrgs.add(subOrgName);
                parentOrg.createSubOrganizations(subOrgs);
            }
        } catch (AMException ame) {
            // Ignore if it is Organization already exists
            if (!ame.getErrorCode().equals("474")) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK::createSubOrganization"
                            + ": failed with AMException", ame);
                }
                throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame
                        .getMessage(), ame, ame.getMessage()));
            }
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Returns the set of assigned services for the organization
     */
    Set getAssignedServices() throws SMSException {
        try {
            if (hasReadPermissionOnly) {
                return (parentOrgWithAdminToken.getRegisteredServiceNames());
            } else {
                return (parentOrg.getRegisteredServiceNames());
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::getAssignedServices"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Assigns the service to the organization
     */
    void assignService(String serviceName) throws SMSException {
        try {
            // Check if it is a hidden realm
            if (ServiceManager.isCoexistenceMode() &&
                (parentOrgName.startsWith(SMSEntry.SUN_INTERNAL_REALM_PREFIX)))
            {
                return;
            }
            // Check if service is already assigned
            if (!getAssignedServices().contains(serviceName)) {
                parentOrg.registerService(serviceName, false, false);
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::assignService"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Unassigns the service from the organization
     */
    void unassignService(String serviceName) throws SMSException {
        try {
            // Check if service is already unassigned
            if (getAssignedServices().contains(serviceName)) {
                parentOrg.unregisterService(serviceName);
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::unassignService"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Returns sub-organization names using AMSKK APIs. The returned names are
     * in "/" separated format and are normailized using DNMapper.
     */
    Set getSubOrganizationNames(String pattern, boolean recursive)
            throws SMSException {
        try {
            // Search for sub-organization names
            Set subOrgDNs;
            if (hasReadPermissionOnly) {
                subOrgDNs = parentOrgWithAdminToken.searchSubOrganizations(
                        pattern, recursive ? AMConstants.SCOPE_SUB
                                : AMConstants.SCOPE_ONE);
            } else {
                subOrgDNs = parentOrg.searchSubOrganizations(pattern,
                        recursive ? AMConstants.SCOPE_SUB
                                : AMConstants.SCOPE_ONE);
            }
            // Convert DNs to "/" seperated relam names
            if (subOrgDNs != null && !subOrgDNs.isEmpty()) {
                Set subOrgs = new HashSet();
                for (Iterator items = subOrgDNs.iterator(); items.hasNext();) {
                    subOrgs.add(DNMapper.orgNameToDN((String) items.next()));
                }
                return SMSEntry.parseResult(subOrgs, smsOrgName);
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::getSubOrganizationNames"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
        return (Collections.EMPTY_SET);
    }

    /**
     * Deletes sub-organiation using AMSDK. If recursive flag is set, then all
     * sub-entries are also removed. Else if sub-entries are present this will
     * throw an exception.
     */
    void deleteSubOrganization(String subOrgName) throws SMSException {
        try {
            // Check if subOrgName is empty or null
            if (subOrgName == null || subOrgName.trim().length() == 0) {
                if (parentOrg.isExists()) {
                    parentOrg.delete(true);
                }
                return;
            }

            // Check if it is a hidden realm
            if (subOrgName.startsWith(SMSEntry.SUN_INTERNAL_REALM_NAME)) {
                return;
            }

            // Get the suborg DN
            Set subOrgDNs = parentOrg.searchSubOrganizations(subOrgName,
                    AMConstants.SCOPE_ONE);
            if (subOrgDNs != null && !subOrgDNs.isEmpty()) {
                for (Iterator items = subOrgDNs.iterator(); items.hasNext();) {
                    String dn = (String) items.next();
                    AMOrganization subOrg = parentOrg.getSubOrganization(dn);
                    if (subOrg != null) {
                        subOrg.delete(true);
                    }
                }
            } else {
                AMOrganization subOrg = parentOrg
                        .getSubOrganization(subOrgName);
                if (subOrg != null) {
                    subOrg.delete(true);
                }
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::deleteSubOrganization"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
    }

    /**
     * Returns the AMSDK Organization attributes. The return attributes are
     * defined in the IdRepo service and can be configured per organization.
     */
    Map getAttributes() throws SMSException {
        Map answer = null;
        try {
            // Get the list of attribute names
            Map attrMapping = getReverseAttributeMapping();
            Set attrNames = attrMapping.keySet();
            if (!attrNames.isEmpty()) {
                // Perform AMSDK search
                Map attributes;
                if (hasReadPermissionOnly) {
                    attributes = parentOrgWithAdminToken
                            .getAttributes(attrNames);
                } else {
                    attributes = parentOrg.getAttributes(attrNames);
                }
                if (attributes != null && !attributes.isEmpty()) {
                    // Do reverse name mapping, and copy to answer
                    for (Iterator items = attributes.keySet().iterator(); items
                            .hasNext();) {
                        String key = (String) items.next();
                        Set values = (Set) attributes.get(key);
                        if (values != null && !values.isEmpty()) {
                            if (answer == null) {
                                answer = new HashMap();
                            }
                            answer.put(attrMapping.get(key), values);
                        }
                    }
                }
            }
        } catch (AMException ame) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK::getAttributes"
                        + ": failed with AMException", ame);
            }
            throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame.getMessage(),
                    ame, ame.getMessage()));
        } catch (SSOException ssoe) {
            throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
                    ssoe, "sms-INVALID_SSO_TOKEN"));
        }
        return (answer == null ? Collections.EMPTY_MAP : answer);
    }

    /**
     * Adds attributes to AMSDK Organization. The organziation attribute names
     * are defined in the IdRepo service.
     */
    void addAttributeValues(String attrName, Set values) throws SMSException {
        // Get the attribute values, add the new values
        // and set the attribute
        if (attrName != null && values != null && !values.isEmpty()) {
            // First get the attribute values, remove the
            // specified valued and then set the attributes
            Map attrs = getAttributes();
            Set origValues = (Set) attrs.get(attrName);
            Set newValues = new HashSet(values);
            if (origValues != null && !origValues.isEmpty()) {
                newValues.addAll(origValues);
            }
            Map newAttrs = new HashMap();
            newAttrs.put(attrName, newValues);
            setAttributes(newAttrs);
        }
    }

    /**
     * Sets attributes to AMSDK Organization. The organziation attribute names
     * are defined in the IdRepo service.
     */
    void setAttributes(Map attributes) throws SMSException {
        Map amsdkAttrs = null;
        // Need to get attributes such as domain name, alias names
        // and org status from attributes and set them.
        // These attributes must be defined in ../idm/xml/idRepoService.xml
        if (attributes != null && !attributes.isEmpty()) {
            Map smsIdRepoAttrs = new CaseInsensitiveHashMap(attributes);
            // Iterate through the attribute mappings
            Map attrs = getAttributeMapping();
            Map existingAttributes = getAttributes();
            if (attrs != null && !attrs.isEmpty()) {
                for (Iterator items = attrs.keySet().iterator(); items
                        .hasNext();) {
                    String key = (String) items.next();
                    Set value = (Set) smsIdRepoAttrs.get(key);
                    if (value != null) {
                        if (amsdkAttrs == null) {
                            amsdkAttrs = new HashMap();
                        }
                        boolean notEmptyFlg = false;
                        if (!value.isEmpty()) {
                            for (Iterator iter = value.iterator(); iter
                                    .hasNext();) {
                                String val = (String) iter.next();
                                // Avoid empty string storage.
                                if (val.length() > 0) {
                                    notEmptyFlg = true;
                                }
                            }
                            if (notEmptyFlg) {
                                amsdkAttrs.put(attrs.get(key), value);
                            }
                        } else {
                            Set existingValues = (Set) existingAttributes
                                    .get(key);
                            if (existingValues != null
                                    && !existingValues.isEmpty()) {
                                amsdkAttrs.put(attrs.get(key), value);
                            }
                        }
                    }
                }
            }
        }

        // Update the organization entry
        if (amsdkAttrs != null) {
            try {
                parentOrg.setAttributes(amsdkAttrs);
                parentOrg.store();
            } catch (AMException ame) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK::createSub"
                            + "Organization: failed with AMException", ame);
                }
                throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame
                        .getMessage(), ame, ame.getMessage()));
            } catch (SSOException ssoe) {
                throw (new SMSException(bundle
                        .getString("sms-INVALID_SSO_TOKEN"), ssoe,
                        "sms-INVALID_SSO_TOKEN"));
            }
        }
    }

    /**
     * Removes the specified attribute from AMSDK organization. The organziation
     * attribute names are defined in the IdRepo service.
     */
    void removeAttribute(String attrName) throws SMSException {
        if (attrName == null) {
            return;
        }

        // Get the attribute mapping and removed specified attribute
        Map attrMap = getAttributeMapping();
        String amsdkAttrName = (String) attrMap.get(attrName);
        if (amsdkAttrName != null) {
            HashSet set = new HashSet();
            set.add(amsdkAttrName);
            try {
                parentOrg.removeAttributes(set);
                parentOrg.store();
            } catch (AMException ame) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK::removeAttribute"
                            + ": failed with AMException", ame);
                }
                throw (new SMSException(AMSDKBundle.BUNDLE_NAME, ame
                        .getMessage(), ame, ame.getMessage()));
            } catch (SSOException ssoe) {
                throw (new SMSException(bundle
                        .getString("sms-INVALID_SSO_TOKEN"), ssoe,
                        "sms-INVALID_SSO_TOKEN"));
            }
        }
    }

    /**
     * Removes the specified attribute values from AMSDK organization. The
     * organziation attribute names are defined in the IdRepo service.
     */
    void removeAttributeValues(String attrName, Set values) throws SMSException 
    {
        if (attrName != null) {
            // First get the attribute values, remove the
            // specified valued and then set the attributes
            Map attrs = getAttributes();
            Set origValues = (Set) attrs.get(attrName);
            if (origValues != null && !origValues.isEmpty()) {
                Set newValues = new HashSet(origValues);
                newValues.removeAll(values);
                if (newValues.isEmpty()) {
                    removeAttribute(attrName);
                } else {
                    Map newAttrs = new HashMap();
                    newAttrs.put(attrName, newValues);
                    setAttributes(newAttrs);
                }
            }
        }
    }

    /**
     * Returns the SMS attribute name to AMSDK attribute name mappings for the
     * organization
     */
    private Map getAttributeMapping() throws SMSException {
        if (!ServiceManager.isConfigMigratedTo70()) {
            return (notMigratedAttributeMappings);
        }
        // Check the cache
        Map answer = (Map) attributeMappings.get(parentOrgName);
        if (answer != null)
            return (answer);

        // Construct the attribute mappings
        Map attrs = serviceConfig.getAttributes();
        if (attrs != null && !attrs.isEmpty()) {
            Set mapAttrs = (Set) attrs.get(MAPPING_ATTR_NAME);
            if (mapAttrs != null && !mapAttrs.isEmpty()) {
                for (Iterator items = mapAttrs.iterator(); items.hasNext();) {
                    String attrMapping = (String) items.next();
                    String[] maps = DNMapper.splitString(attrMapping);
                    if (answer == null) {
                        answer = new CaseInsensitiveHashMap();
                    }
                    answer.put(maps[0], maps[1]);
                }
            }
        }
        if (answer == null) {
            answer = Collections.EMPTY_MAP;
        }
        // Add to cache
        attributeMappings.put(parentOrgName, answer);
        return (answer);
    }

    /**
     * Returns the AMSDK attribute name to SMS attribute name mappings for the
     * organization
     */
    private Map getReverseAttributeMapping() throws SMSException {
        if (!ServiceManager.isConfigMigratedTo70()) {
            return (notMigratedReverseAttributeMappings);
        }
        // Check the cache
        Map answer = (Map) reverseAttributeMappings.get(parentOrgName);
        if (answer != null)
            return (answer);

        // Get the attribute mapping and reverse it
        Map attrMaps = getAttributeMapping();
        for (Iterator items = attrMaps.entrySet().iterator(); items.hasNext();) 
        {
            Map.Entry entry = (Map.Entry) items.next();
            if (answer == null) {
                answer = new CaseInsensitiveHashMap();
            }
            answer.put(entry.getValue(), entry.getKey().toString());
        }
        if (answer == null) {
            answer = Collections.EMPTY_MAP;
        }
        reverseAttributeMappings.put(parentOrgName, answer);
        return (answer);
    }

    // Check to see if the user has realm permissions
    private boolean checkRealmPermission(SSOToken token, String realm,
        Set action) {
        boolean answer = false;
        if (token != null) {
            try {
                DelegationEvaluator de = new DelegationEvaluatorImpl();
                DelegationPermission dp = new DelegationPermission(realm,
                    com.sun.identity.sm.SMSEntry.REALM_SERVICE, "1.0", "*",
                    "*", action, Collections.EMPTY_MAP);
                answer = de.isAllowed(token, dp, null);
            } catch (DelegationException dex) {
                debug.error("OrgConfigViaAMSDK.checkRealmPermission: "
                    + "Got Delegation Exception: ", dex);
            } catch (SSOException ssoe) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK.checkRealmPermission: "
                        + "Invalid SSOToken: ", ssoe);
                }
            }
        }
        return (answer);
    }
    
    static String getNamingAttrForOrg() {
        return (ServiceManager.isAMSDKEnabled()) ?
            AMNamingAttrManager.getNamingAttr(AMObject.ORGANIZATION) :
            SMSEntry.ORG_PLACEHOLDER_RDN;
    }
    
    static String getNamingAttrForOrgUnit() {
        return AMNamingAttrManager.getNamingAttr(AMObject.ORGANIZATIONAL_UNIT);
    }
    
    public Set getSDKAttributeValue(String key) {
        Set attrSet = new HashSet();
        try {
            attrSet = parentOrg.getAttribute(key);
        } catch (AMException ame) {
            if (debug.warningEnabled()) {
                debug.warning("OrgConfigViaAMSDK::getSDKAttributeValue"
                    + ": failed with AMException", ame);
            }
        } catch (SSOException ssoe) {
            if (debug.warningEnabled()) {
                debug.warning("OrgConfigViaAMSDK::getSDKAttributeValue"
                    + ": failed with SSOException", ssoe);
            }
        }
        return (attrSet);
    }
    
    /**
     * Clears the cache
     */
    protected static void clearCache() {
        attributeMappings = new CaseInsensitiveHashMap();
        reverseAttributeMappings = new CaseInsensitiveHashMap();
        amsdkdn2realmname = new CaseInsensitiveHashMap();
        amsdkConfiguredRealms = new CaseInsensitiveHashMap();
    }
    
    protected static void updateAMSDKConfiguredRealms(
        String realm, boolean configured) {
        if (!amsdkConfiguredRealms.keySet().contains(realm)) {
            amsdkConfiguredRealms.put(realm, Boolean.valueOf(configured));
        }
    }
    
    /**
     * Returns the true if AMSDK plugin is configured for the realm,
     * else returns false.
     */
    public static boolean isAMSDKConfigured(String realm) {
        if (ServiceManager.isCoexistenceMode()) {
            return (true);
        }
        // Check the cache
        realm = DNUtils.normalizeDN(realm);
        Boolean answer = (Boolean) amsdkConfiguredRealms.get(realm);
        if (answer == null) {
            try {
                SSOToken token = (SSOToken) AccessController.doPrivileged(
                    AdminTokenAction.getInstance());
                OrganizationConfigManagerImpl ocm =
                    OrganizationConfigManagerImpl.getInstance(token, realm);
                String orgname = getAmsdkdn(token, ocm);
                answer = Boolean.valueOf(orgname != null);
            } catch (SSOException ssoe) {
                answer = Boolean.FALSE;
            } catch (SMSException smse) {
                answer = Boolean.FALSE;
            }
            // Update cache
            amsdkConfiguredRealms.put(realm, answer);
        }
        return (answer.booleanValue());
    }
    
    /**
     * Returns the realm name that contains the AMSDK plugin with the
     * given organization dn. The function optionally takes "inrealm"
     * the realm, where the initial search would be done
     * If not found, returns null.
     */
    public static String getRealmForAMSDK(String amsdkdn,
        String inrealm) {
        // If in legacy mode, return amsdkdn
        if (ServiceManager.isCoexistenceMode()) {
            return (amsdkdn);
        }
        String realm = inrealm;
        // Check the cache
        amsdkdn = DNUtils.normalizeDN(amsdkdn);
        // if amsdk was not in DN format then normalizeDN will return null
        if(amsdkdn == null) {
            return null;
        }
        String orgname = (String) amsdkdn2realmname.get(amsdkdn);
        if (orgname != null) {
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK:getRealmForAMSDK " +
                    "from cache: orgdn=" + amsdkdn + " realm=" + orgname);
            }
            return (orgname);
        }
        
        // First check with "inrealm" and then with "amsdkdn"
        OrganizationConfigManagerImpl ocm = null;
        SSOToken token = (SSOToken) AccessController.doPrivileged(
            AdminTokenAction.getInstance());
        try {
            // Check inrealm first
            if (inrealm != null) {
                ocm = OrganizationConfigManagerImpl.getInstance(
                    token, inrealm);
                orgname = getAmsdkdn(token, ocm);
            }
            
            // Need to check for the following conditions before
            // using amsdkdn as the realm name to determine the
            // AMSDK plugin organization name
            // i) "inrealm" is null  (realm name is not provided)
            // ii) orgname != null && !orgname.equals(realm)
            //   (since orgname is not null, AMSDK has been configured
            //   configured for the realm, but it does not match the
            //   provided "amsdkdn", hence need to check for amsdkdn realm
            // iii) !inrealm.equals(amsdkdn)
            //   If same, the check has been done. No need to repeat
            // iv) If the dn starts with ou then the realm for the orgUnit
            //     is hidden. So first replace values of all ou's in the
            //     amsdkdn and then find the realm for it.
            if ((inrealm == null) ||
                ((orgname != null) && !orgname.equals(realm)) ||
                ((orgname != null) &&
                !amsdkdn.equals(DNUtils.normalizeDN(inrealm)))) {
                String dn = hideOrgUnits(amsdkdn);
                ocm = OrganizationConfigManagerImpl.getInstance(
                    token, dn);
                orgname = getAmsdkdn(token, ocm);
                if ((orgname != null) && orgname.equals(amsdkdn)) {
                    realm = ocm.getOrgDN();
                }
            }
        } catch (SMSException sme) {
            // Ignore the exception, since the realm is not present
            // and an explicit search would be done below
        } catch (SSOException ssoe) {
            // Ignore the exception, since the realm is not present
            // and an explicit search would be done below
        }
        
        if (realm != null) {
            amsdkdn2realmname.put(amsdkdn, realm);
            if (debug.messageEnabled()) {
                debug.message("OrgConfigViaAMSDK:getRealmForAMSDK " +
                    "first realm lookup: orgdn=" + amsdkdn +
                    " realm=" + realm);
            }
        } else {
            // If realm is still null, need to search the realm tree
            try {
                ocm = OrganizationConfigManagerImpl.getInstance(token, "/");
                updateAmsdk2RealmNameCache(token, ocm, amsdkdn);
                realm = (String) amsdkdn2realmname.get(amsdkdn);
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK:getRealmForAMSDK " +
                        "full search orgdn=" + amsdkdn + " realm=" + realm);
                }
            } catch (SMSException e) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK:getRealmForAMSDK" +
                        " Exception: ", e);
                }
            } catch (SSOException ssoe) {
                if (debug.messageEnabled()) {
                    debug.message("OrgConfigViaAMSDK:getRealmForAMSDK" +
                        " SSException: ", ssoe);
                }
            }
        }
        return (realm);
    }
    
    /**
     * This method checks if the dn starts with org unit naming attr.
     * If yes, then it replaces values of all ou's by prefixing
     * SMSEntry.SUN_INTERNAL_REALM_NAME because all realms mapping to
     * orgUnits are hidden.
     * If the dn does not start with org unit naming attr then it is
     * returned as-is.
     * For example,
     *      ou=X,ou=Y,o=DevSample,dc=red,dc=iplanet,dc=com
     *      is replaced with
     *      ou=sunamhiddenrealmX,ou=sunamhiddenrealmY,o=DevSample,dc=red,dc=iplanet,dc=com
     *
     * @param orgUnitDN String can not be null
     */
    private static String hideOrgUnits(String orgUnitDN) {
        String ou = getNamingAttrForOrgUnit();
        if(!orgUnitDN.startsWith(ou)) {
            return orgUnitDN;
        }
        DN result = DN.rootDN();
        DN rdns = DN.valueOf(orgUnitDN);
        for (int i = rdns.size() - 1; i >= 0; i--) {
            RDN rdn = rdns.parent(i).rdn();
            if(rdnType(rdn).equals(ou)) {
                result = result.child(new RDN(ou, SMSEntry.SUN_INTERNAL_REALM_NAME + rdnValue(rdn)));
            } else {
                result = result.child(rdn);
            }
        }
        return result.toString();
    }
    
    private static boolean updateAmsdk2RealmNameCache(SSOToken token,
        OrganizationConfigManagerImpl ocm, String amsdkdn)
        throws SMSException, SSOException {
        boolean foundEntry = false;
        // Get the AMSDK DN configured for the realm, update cache
        String orgname = getAmsdkdn(token, ocm);
        if (orgname != null) {
            amsdkdn2realmname.put(orgname, ocm.getOrgDN());
            if (orgname.equals(amsdkdn)) {
                foundEntry = true;
            }
        }
        
        // Walk down the realm tree if entry is not found
        if (!foundEntry) {
            Set subRealmNames = ocm.getSubOrganizationNames(token);
            if ((subRealmNames != null) && !subRealmNames.isEmpty()) {
                for (Iterator realms = subRealmNames.iterator();
                realms.hasNext();) {
                    OrganizationConfigManagerImpl socm =
                        OrganizationConfigManagerImpl.getInstance(
                        token, "o=" + realms.next() + "," +
                        ocm.getOrgDN());
                    if ((foundEntry = updateAmsdk2RealmNameCache(
                        token, socm, amsdkdn))) {
                        break;
                    }
                }
            }
        }
        return (foundEntry);
    }
    
    public static String getAmsdkdn(SSOToken token,
        OrganizationConfigManagerImpl ocm)
        throws SMSException, SSOException {
        if (ServiceManager.isCoexistenceMode()) {
            return ocm.getOrgDN();
        }
        String orgdn = null;
        // Get idrepo plugins and check for amsdkdn plugin
        ServiceConfigManagerImpl sci = ServiceConfigManagerImpl
            .getInstance(token, ServiceManager.REALM_SERVICE, "1.0");
        if (sci != null) {
            ServiceConfigImpl sc = sci.getOrganizationConfig(
                token, ocm.getOrgDN(), null);
            if (sc != null) {
                Set plugins = sc.getSubConfigNames(token);
                if (plugins != null && !plugins.isEmpty()) {
                    for (Iterator items = plugins.iterator();
                    items.hasNext();) {
                        ServiceConfigImpl ssc = sc.getSubConfig(
                            token, (String) items.next());
                        if (ssc.getSchemaID().equalsIgnoreCase(
                            IdConstants.AMSDK_PLUGIN_NAME)) {
                            Map cMap = ssc.getAttributesForRead();
                            if ((cMap != null) && !cMap.isEmpty()) {
                                Set orgs = (Set) cMap.get("amSDKOrgName");
                                if ((orgs != null) && !orgs.isEmpty()) {
                                    orgdn = DNUtils.normalizeDN(
                                        (String) orgs.iterator().next());
                                }
                            }
                            break;
                        }
                    }
                }
            }
        }
        return (orgdn);
    }
    
    // Returns the organization type for AMSDK DN.
    private int getObjectType() {
        if (objType == 0) {
            try {
                AMStoreConnection amcom = new AMStoreConnection(
                    (SSOToken) AccessController.doPrivileged(
                    AdminTokenAction.getInstance()));
                objType = amcom.getAMObjectType(parentOrgName);
            } catch(AMException ame) {
                // set as organizational unit
                objType = AMObject.ORGANIZATIONAL_UNIT;
                debug.error("OrgConfigViaAMSDK: Unable to determine type");
            } catch (SSOException ssoe) {
                // set as organizational unit
                objType = AMObject.ORGANIZATIONAL_UNIT;
            }
        }
        return (objType);
    }
}
