/**
 * 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: AMAuthenticationManager.java,v 1.9 2009/08/05 19:57:27 qcheng Exp $
 *
 * Portions Copyrighted 2011-2015 ForgeRock AS.
 * Portions Copyrighted 2014 Nomura Research Institute, Ltd
 */
package com.sun.identity.authentication.config;

import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.iplanet.am.util.SystemProperties;
import com.sun.identity.authentication.service.AuthUtils;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.common.DNUtils;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.xml.XMLUtils;
import com.sun.identity.sm.SchemaType;
import com.sun.identity.sm.ServiceConfig;
import com.sun.identity.sm.ServiceConfigManager;
import com.sun.identity.sm.ServiceSchema;
import com.sun.identity.sm.ServiceSchemaManager;
import com.sun.identity.sm.OrganizationConfigManager;
import com.sun.identity.sm.SMSEntry;
import com.sun.identity.sm.SMSException;

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.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * This class provides interfaces to manage authentication module instances. 
 */
public class AMAuthenticationManager {

    private static final String BUNDLE_NAME = "amAuthConfig";
    private static final Debug DEBUG = Debug.getInstance(BUNDLE_NAME);
    private static final Set<String> AUTH_TYPES = new HashSet<String>();
    private static final Map<String, String> MODULE_SERVICE_NAMES = new ConcurrentHashMap<String, String>();
    private static final Set<String> GLOBAL_MODULE_NAMES = new HashSet<String>();
    private static final Map<String, Map<String, Set<String>>> MODULE_INSTANCE_TABLE = Collections.synchronizedMap(
            new HashMap<String, Map<String, Set<String>>>());
    private SSOToken token;
    private String realm;
    private ServiceConfig orgServiceConfig;

    static {
        initAuthenticationService();
    }

    /**
     * Constructs an instance of <code>AMAuthenticationManager</code> for the specified realm to manage the
     * authentication module instances available to this realm.
     *
     * @param token Single sign on token of the user identity on whose behalf the operations are performed.
     * @param org The realm in which the module instance management is performed.
     * @throws AMConfigurationException if Service Management related error occurs.
     */
    public AMAuthenticationManager(SSOToken token, String org) throws AMConfigurationException {
        try {
            SMSEntry.validateToken(token);
            this.token = token;
            this.realm = DNUtils.normalizeDN(com.sun.identity.sm.DNMapper.orgNameToDN(org));

            orgServiceConfig = getOrgServiceConfig();
            if (orgServiceConfig == null) {
                throw new AMConfigurationException(BUNDLE_NAME, "badRealm",
                new Object[]{realm});
            }
            synchronized (AMAuthenticationManager.class) {
                if (!MODULE_INSTANCE_TABLE.containsKey(realm)) {
                    buildModuleInstanceTable(token, realm);
                }
            }
        } catch (SMSException e) {
            throw new AMConfigurationException(e);
        } catch (Exception ee) {
            String installTime = SystemProperties.get(
                AdminTokenAction.AMADMIN_MODE);
            if ((installTime != null) && installTime.equalsIgnoreCase("false")){
                DEBUG.error("Token is invalid." , ee);
            }
        }
    }

    /**
     * Re-initializes the module services.
     * This method is meant for global authentication configuration change.
     */
    public static synchronized void reInitializeAuthServices() {
        AUTH_TYPES.clear();
        GLOBAL_MODULE_NAMES.clear();
        initAuthenticationService();
    }

    /**
     * Returns a Set contains all the authentication types that are plugged in
     * this server. 
     * @return Set of String values of the authentication types available on 
     *         this server.
     */
    public static Set<String> getAuthenticationTypes() {
        return AUTH_TYPES;
    }

    /**
     * Returns a Set contains all the module service names that are plugged in
     * this server. 
     * @return Set of String values of the module service names available on 
     *         this server.
     */
    public static Set<String> getAuthenticationServiceNames() {
        Set<String> names = new HashSet<String>(MODULE_SERVICE_NAMES.values());

        if (DEBUG.messageEnabled()) {
            DEBUG.message("Authenticator serviceNames: " + names);
        }
        return names;
    }

    /**
     * Returns authentication service name of a module.
     *
     * @param moduleName Name of authentication module.
     * @return authentication service name of a module.
     */
    public static String getAuthenticationServiceName(String moduleName) {
        return MODULE_SERVICE_NAMES.get(moduleName);
    }

    /**
     * This code makes the authentication type list static. In case the list
     * is expanded or shrinked, the server needs to be restarted.
     */
    private static void initAuthenticationService() {
        SSOToken token = getAdminToken();
        try {
            ServiceSchemaManager scm = new ServiceSchemaManager(ISAuthConstants.AUTH_SERVICE_NAME, token);
            ServiceSchema schema = scm.getGlobalSchema();
            Set<String> authenticators = (Set<String>) schema.getAttributeDefaults().get(
                    ISAuthConstants.AUTHENTICATORS);
            for (String module : authenticators) {
                int index = module.lastIndexOf(".");
                if (index != -1) {
                    module = module.substring(index + 1);
                }
                // Application is not one of the selectable instance type.
                if (!module.equals(ISAuthConstants.APPLICATION_MODULE)) { 
                    AUTH_TYPES.add(module);
                }
                String serviceName = MODULE_SERVICE_NAMES.get(module);
                if (serviceName == null) {
                    serviceName = AuthUtils.getModuleServiceName(module);
                    try {
                        new ServiceSchemaManager(serviceName, token);
                        MODULE_SERVICE_NAMES.put(module, serviceName);
                    } catch (Exception e) {
                        GLOBAL_MODULE_NAMES.add(module);
                        AUTH_TYPES.remove(module);
                    }
                }
            }
            if (DEBUG.messageEnabled()) {
                DEBUG.message("Global module names: " + GLOBAL_MODULE_NAMES);
                DEBUG.message("moduleServiceNames: " + MODULE_SERVICE_NAMES);
            }
       } catch (Exception smse) {
            String installTime = SystemProperties.get(
                AdminTokenAction.AMADMIN_MODE);
            if ((installTime != null) && installTime.equalsIgnoreCase("false")){
                DEBUG.error("Failed to get module types", smse);
            }
        }
    }
    
    /**
     * build the module instance table for the realm.
     * format of this table:
     * Table:  key = realm, value = module Map for the realm.
     * module Map for the realm: 
     *   key = module type, value = Set of module instances
     */
    private static void buildModuleInstanceTable(SSOToken token, String realm) {
        try {
            if (DEBUG.messageEnabled()) {
                DEBUG.message("AMAuthenticationManager." +
                    "buildModuleInstanceTable: realm = " + realm);
            }
            for (String service : MODULE_SERVICE_NAMES.values()) {
                buildModuleInstanceForService(realm, service);
            }
        } catch (Exception e) {
            if (DEBUG.messageEnabled()) {
                DEBUG.message("building module instance table error", e);
            }
        }
    }

    /**
     * Updates the static module instance table for the specified service in
     * the realm.
     *
     * @param realm The realm in which the operation is processed.
     * @param serviceName the service for which the table is built.
     */
    private static synchronized void buildModuleInstanceForService(
        String realm,
        String serviceName) {
        if (DEBUG.messageEnabled()) {
            DEBUG.message("start moduleInstanceTable : " + MODULE_INSTANCE_TABLE +
            " for realm : " + realm + " and service : " + serviceName);
        }
        try {
            String moduleName = getModuleName(serviceName);
            if (DEBUG.messageEnabled()) {
                DEBUG.message("Module name : " + moduleName);
            }
            if ((moduleName != null) && (moduleName.length() != 0)) {
                ServiceConfigManager scm = new ServiceConfigManager(serviceName, getAdminToken());
                ServiceConfig config = scm.getOrganizationConfig(realm, null);
                if (config == null) {
                    if (DEBUG.messageEnabled()) {
                        DEBUG.message("AMAuthenticationManager." +
                            "buildModuleInstanceForService: Service="
                            + serviceName + " not configured in realm=" + realm);
                    }
                }
                realm = DNUtils.normalizeDN(com.sun.identity.sm.DNMapper.orgNameToDN(realm));
                synchronized (MODULE_INSTANCE_TABLE) {
                    Map<String, Set<String>> moduleMap = MODULE_INSTANCE_TABLE.remove(realm);
                    if (moduleMap != null) {
                    /*
                     * this code is to not manipulate the hashmap that might
                     * be in iteration by other threads
                     */
                        Map<String, Set<String>> newMap = new HashMap<String, Set<String>>(moduleMap);
                        newMap.remove(moduleName);
                        moduleMap = newMap;
                    }
                    Set<String> instanceSet = new HashSet<String>();
                    Map<String, Set<String>> defaultAttrs = null;
                    if (config != null) {
                        defaultAttrs = config.getAttributesWithoutDefaults();
                    }
                    if (defaultAttrs != null && !defaultAttrs.isEmpty()) {
                        instanceSet.add(moduleName);
                    }
                    Set<String> instances = null;
                    if (config != null) {
                        instances = config.getSubConfigNames();
                    }
                    if (instances != null) {
                        instanceSet.addAll(instances);
                    }
                    if (!instanceSet.isEmpty()){
                        if (moduleMap == null) {
                            moduleMap = new HashMap<String, Set<String>>();
                        }
                        /*
                         * this operation is safe as moduleMap is a local object
                         * now.
                         */
                        moduleMap.put(moduleName, instanceSet);
                    }
                    if (moduleMap != null && !moduleMap.isEmpty()) {
                        MODULE_INSTANCE_TABLE.put(realm, moduleMap);
                    }
                }
            }
        } catch (Exception e) {
            if (DEBUG.messageEnabled()) {
                DEBUG.message("build module instance for service error: " , e);
            }
        }
        if (DEBUG.messageEnabled()) {
            DEBUG.message("return moduleInstanceTable: " + MODULE_INSTANCE_TABLE);
        }
    }

    /**
     * Updates the module instance table for the authentication service if the module instance table was already
     * cached for the provided realm.
     *
     * @param realm The realm where the configuration has changed.
     * @param serviceName The authentication module's service name.
     */
    public static synchronized void updateModuleInstanceTable(String realm, String serviceName) {
        realm = com.sun.identity.sm.DNMapper.orgNameToDN(realm);
        if (MODULE_INSTANCE_TABLE.containsKey(realm)) {
            buildModuleInstanceForService(realm, serviceName);
        }
    }

    // get the module name from its service name.
    private static String getModuleName(String serviceName) {
        for (String moduleName : MODULE_SERVICE_NAMES.keySet()) {
            if (MODULE_SERVICE_NAMES.get(moduleName).equals(serviceName)) {
                return moduleName;
            }
        }
        return null;
    }

    /**
     * Returns an <code>AMAuthenticationSchema</code> object for the specified
     * authentication type. 
     *
     * @param authType Type of the authentication module instance.
     * @return <code>AMAuthenticationSchema</code> object of the specified
     *         authentication type.
     * @throws AMConfigurationException if error occurred during retrieving
     *         the service schema.
     */
    public AMAuthenticationSchema getAuthenticationSchema(String authType) 
            throws AMConfigurationException {
        return getAuthenticationSchema(authType, token);
    }

    private static AMAuthenticationSchema getAuthenticationSchema(
        String authType, SSOToken token) throws AMConfigurationException {
        if (DEBUG.messageEnabled()) {
            DEBUG.message("getting auth schema for " + authType);
        }
        try {
            ServiceSchema serviceSchema;
            String serviceName = getServiceName(authType);
            ServiceSchemaManager scm = new ServiceSchemaManager(serviceName, 
                token);
            ServiceSchema orgSchema = scm.getOrganizationSchema();
            ServiceSchema subSchema = orgSchema.getSubSchema(
                ISAuthConstants.SERVER_SUBSCHEMA);
            if (subSchema != null) {
                  // using the sub schema in new auth config.
                serviceSchema = subSchema;
            } else {
                // fall back to the org schema if the DIT is old.
                serviceSchema = orgSchema;
            }
            AMAuthenticationSchema amschema = 
                new AMAuthenticationSchema(serviceSchema);
            return amschema;
        } catch (Exception e) {
            throw new AMConfigurationException(e);
        }
    }

    /**
     * Returns the <code>AMAuthenticationInstance</code> object whose name is
     * as specified.
     * Name uniqueness is required for the instances among the same realm, as 
     * well as the instances that are available to this realm.
     *
     * @param authName Authentication instance name.
     * @return The <code>AMAuthenticationInstance</code> object that is
     *         associated with the authentication instance name.
     */
    public AMAuthenticationInstance getAuthenticationInstance(String authName) {
        String type = getAuthInstanceType(authName);
        if (type == null) {
            return null;
        }
        return getAuthenticationInstance(authName, type);
    }

    /**
     * Returns an <code>AMAuthenticationInstance</code> object with the give
     * authentication name and type.
     */
    private AMAuthenticationInstance getAuthenticationInstance(
        String authName,
        String type){
        // for global authentication modules
        if (GLOBAL_MODULE_NAMES.contains(authName)) {
            return new AMAuthenticationInstance(authName, type, null, null);
        }
        String serviceName = getServiceName(type);
        AMAuthenticationInstance instance = null;
        ServiceConfigManager scm = null;
        ServiceSchemaManager ssm = null;
        try {
            ssm = new ServiceSchemaManager( serviceName, token);
        } catch (SMSException e) {
            if (DEBUG.messageEnabled()) {
                DEBUG.message("Instance type does not exist: " + type);
            }
            return null;
        } catch (SSOException ee) {
            DEBUG.error("SSO token is invalid", ee);
            return null;
        }
        Map globalAttrs = null;
        ServiceSchema schema = null;
        try {
            schema = ssm.getSchema(SchemaType.GLOBAL);
            if (schema != null) {
                globalAttrs = schema.getAttributeDefaults();
            }
        } catch (SMSException e) {
            // normal exception for some schemas without global configuration.
            // no need to log anything.
        }

        Map orgAttrs = null;
        ServiceConfig service = null;
        try {
            scm = new ServiceConfigManager(serviceName, token);
            service = scm.getOrganizationConfig(realm, null);
                    
            if (service != null) {
                if (authName.equals(type)
                        // Must check if there is a sub-config with the auth
                        // type as the name otherwise it will not be returned.
                        && service.getSubConfig(authName) == null) {
                    orgAttrs = service.getAttributesWithoutDefaults();
                } else {
                    service = service.getSubConfig(authName);
                    if (service != null) {
                          orgAttrs = service.getAttributes();
                    }
                }
            }
        } catch (SSOException e) {
            if (DEBUG.warningEnabled()) {
                DEBUG.warning("Token doesn't have access to service: " +
                   token + " :: " + serviceName); 
            }
        } catch (SMSException e) {
            // normal exception for global service configuration.
            // no need to log anything. 
        }

        if (DEBUG.messageEnabled()) {
            DEBUG.message("global attrs = " + globalAttrs);
            DEBUG.message("org attrs = ");
            if (orgAttrs != null) {
                for (Iterator it=orgAttrs.entrySet().iterator(); it.hasNext();){
                    Map.Entry e = (Map.Entry) it.next();
                    if ((((String)e.getKey()).endsWith("passwd")) ||
                       (((String)e.getKey()).endsWith("Passwd")) ||
                       (((String)e.getKey()).endsWith("password")) ||
                       (((String)e.getKey()).endsWith("Password")) ||
                       (((String)e.getKey()).endsWith("secret"))) {
                        DEBUG.message(e.getKey() + ": " + "<BLOCKED>");
                    }
                    else {
                        DEBUG.message(e.getKey() + ": " + e.getValue());
                    }
                }
            }
        }

        if ((globalAttrs != null && ! globalAttrs.isEmpty()) ||
            (orgAttrs != null && ! orgAttrs.isEmpty())) {
            return new AMAuthenticationInstance(authName, type,service,schema);
        } else {
            return null;
        }
    }

    /**
     * Returns the type of the authentication module instance with the 
     * specified instance name.
     */
    public String getAuthInstanceType(String authName) {
        String returnValue = null;
        if (GLOBAL_MODULE_NAMES.contains(authName)) {
            returnValue = authName;
        } else {
            Map<String, Set<String>> moduleMap = MODULE_INSTANCE_TABLE.get(realm);
            if (moduleMap != null) {
                for (String type : moduleMap.keySet()) {
                    Set<String> instanceNames = moduleMap.get(type);
                    if (instanceNames.contains(authName)) {
                        returnValue = type;
                        break;
                    }
                }
            }
        }
        return returnValue;
    }
    /**
     * Returns a Set of all registered module instance names for a module type,
     * including both the old instances from 6.3 DIT and the new instances
     * in 7.0.
     */
    public Set<String> getModuleInstanceNames(String aModuleType) {
        Set<String> instances = Collections.EMPTY_SET;
        Map<String, Set<String>> moduleMap = MODULE_INSTANCE_TABLE.get(realm);
        if (moduleMap != null || !GLOBAL_MODULE_NAMES.isEmpty()) {
        instances = new HashSet<String>();
            if (moduleMap != null) {
            for (String key : moduleMap.keySet()) {
                if (key.equals(aModuleType)) {
                    instances.addAll(moduleMap.get(key));
                }
            }
            }
        }
        if (DEBUG.messageEnabled()) {
            DEBUG.message("Registered module names: " + instances);
        }
        return instances;
    }


    /**
     * Returns a Set of all registered module instance names, including 
     * both the old instances from 6.3 DIT and the new instances in 7.0. 
     */
    private Set<String> getRegisteredModuleNames() {
        Set<String> instances = Collections.EMPTY_SET;
        Map<String, Set<String>> moduleMap = MODULE_INSTANCE_TABLE.get(realm);
        if (moduleMap != null || !GLOBAL_MODULE_NAMES.isEmpty()) {
            instances = new HashSet<String>();
            if (moduleMap != null) {
                for (String key : moduleMap.keySet()) {
                    instances.addAll(moduleMap.get(key));
                }
            }
            if (!GLOBAL_MODULE_NAMES.isEmpty()){
                instances.addAll(GLOBAL_MODULE_NAMES);
            }
        }
        if (DEBUG.messageEnabled()) {
            DEBUG.message("Registered module names: " + instances);
        }
        return instances;
    }

    /**
     * Returns a Set of module instance names that is allowed for this
     * organization.
     * Since this is only needed for 6.3 and earlier, for 7.0 it returns an
     * empty set.
     * @return a Set of String values for module instance names.
     */
    public Set<String> getAllowedModuleNames() {
        Set<String> retVal;
        if (AuthUtils.getAuthRevisionNumber() >= ISAuthConstants.AUTHSERVICE_REVISION7_0) {
            retVal = getRegisteredModuleNames();
        } else {
            Map<String, Set<String>> attrMap = orgServiceConfig.getAttributes();
            Set<String> defaultModuleNames = attrMap.get(ISAuthConstants.AUTH_ALLOWED_MODULES);
            Set<String> returnSet = Collections.EMPTY_SET;
            if (defaultModuleNames != null && !GLOBAL_MODULE_NAMES.isEmpty()) {
                   returnSet = new HashSet<String>();
                returnSet.addAll(GLOBAL_MODULE_NAMES);
                returnSet.addAll(defaultModuleNames);
            }
            retVal = returnSet;
        }
        if (retVal != null) {
            retVal.remove(ISAuthConstants.APPLICATION_MODULE);
        }
        return retVal;
    }

    /* return true if this module is from 6.3 DIT */
    private boolean isInheritedAuthInstance(String name) {
        Map attrMap = orgServiceConfig.getAttributes();
        Set defaultModuleNames = (Set)attrMap.get(
            ISAuthConstants.AUTH_ALLOWED_MODULES);
        if (defaultModuleNames != null && defaultModuleNames.contains(name)) {
            return true;
        } 
        return false;
    }

    private ServiceConfig getOrgServiceConfig() {
        try {
            ServiceConfigManager scm = new ServiceConfigManager(
                ISAuthConstants.AUTH_SERVICE_NAME, token);
            return scm.getOrganizationConfig(realm, null);
        } catch (Exception e) {
            String installTime = SystemProperties.get(
                AdminTokenAction.AMADMIN_MODE);
            if ((installTime != null) && installTime.equalsIgnoreCase("false")){
                DEBUG.error("Service config for " + realm + " is null." +
                    e.getMessage());
            }
            return null;
        }
    }

    /**
     * Returns the authentication module instances that are available to this 
     * realm except the Application instance which is for internal use only.
     *
     * @return A Set of <code>AMAuthenticationInstance</code> objects that are 
     *         available to this realm.
     */
    public Set<AMAuthenticationInstance> getAuthenticationInstances() {
        Set<AMAuthenticationInstance> instanceSet = Collections.EMPTY_SET;
        Map<String, Set<String>> moduleMap = MODULE_INSTANCE_TABLE.get(realm);
        if (moduleMap != null || !GLOBAL_MODULE_NAMES.isEmpty()) {
            instanceSet = new HashSet<AMAuthenticationInstance>();
            if (!GLOBAL_MODULE_NAMES.isEmpty()) {
                for (String name : GLOBAL_MODULE_NAMES) {
                    if (name.equals(ISAuthConstants.APPLICATION_MODULE)) {
                        continue;
                    }
                    AMAuthenticationInstance instance = getAuthenticationInstance(name, name);
                    if (instance != null) {
                        instanceSet.add(instance);
                    }
                }
            }
            if (moduleMap != null) {
                for (String type : moduleMap.keySet()) {
                    Set<String> instanceNameSet = moduleMap.get(type);
                    for (String name : instanceNameSet) {
                        AMAuthenticationInstance instance = getAuthenticationInstance(name, type);
                        if (instance != null) {
                            instanceSet.add(instance);
                        }
                    }
                }
            }
        }
        return instanceSet;
    }

    /**
     * Creates an <code>AMAuthenticationInstance</code> instance with the
     * specified parameters.
     *
     * @param name Name of the authentication module instance.
     * @param type Type of the authentication module instance.
     * @param attributes A Map of parameters for this module instance.
     * @return <code>AMAuthenticationInstance</code> object is newly created.
     * @throws AMConfigurationException if error occurred during the 
     *         authentication creation.
     */
    public AMAuthenticationInstance createAuthenticationInstance(
        String name, 
        String type,
        Map attributes
    ) throws AMConfigurationException {
        if (name.indexOf(' ') != -1) {
            throw new AMConfigurationException(BUNDLE_NAME,
                "invalidAuthenticationInstanceName", null);
        }
        Set moduleTypes = getAuthenticationTypes();
        if (!moduleTypes.contains(type)) {
            throw new AMConfigurationException(BUNDLE_NAME, "wrongType",
                new Object[]{type} );
        }
        AMAuthenticationInstance instance = getAuthenticationInstance(name);
        if (instance != null) {
            if (instance.getServiceConfig() != null) {
                throw new AMConfigurationException(BUNDLE_NAME,
                    "authInstanceExist", new Object[]{name});
            } else {
                throw new AMConfigurationException(BUNDLE_NAME,
                    "authInstanceIsGlobal", new Object[]{name});
            }
        }

        String serviceName = getServiceName(type);

        ServiceSchema schema = null;
        try {
            ServiceSchemaManager ssm = 
                new ServiceSchemaManager(serviceName, token);
            schema = ssm.getSchema(SchemaType.GLOBAL);
        } catch (SSOException e) {
            if (DEBUG.warningEnabled()) {
                DEBUG.warning("Token doesn't have access to service: " +
                   token + " -> " + serviceName); 
            }
        } catch (SMSException e) {
            // normal exception for service without global configuration.
            // no need to log anything.
        }

        try {
            OrganizationConfigManager ocm = new OrganizationConfigManager(
                token, realm);
            // Check if service is assigned
            if (!ocm.getAssignedServices().contains(serviceName)) {
                ocm.assignService(serviceName, null);
            }
            ServiceConfig orgConfig = ocm.getServiceConfig(serviceName);
            if (orgConfig == null) {
                orgConfig = ocm.addServiceConfig(serviceName, null);
            }
            ServiceConfig subConfig = orgConfig;
            if (!name.equals(type)) {
                orgConfig.addSubConfig(name, ISAuthConstants.SERVER_SUBSCHEMA, 
                    0, attributes);
                subConfig = orgConfig.getSubConfig(name);
            } else {
                // if the module instance name equals to its type, set the
                // the attributes in its organization config, not sub config.
                subConfig.setAttributes(attributes);
            }

            //In case of server mode AMAuthLevelManager will update AMAuthenticationManager about the change, and
            //there is no need to reinitialize the configuration twice. In client mode it is less likely that
            //AMAuthLevelManager listeners are in place, so let's reinitialize to be on the safe side.
            if (!SystemProperties.isServerMode()) {
                buildModuleInstanceForService(realm, serviceName);
            }
            return new AMAuthenticationInstance(name, type, subConfig, schema);
        } catch (Exception e) {
            throw new AMConfigurationException(e);
        }
    }


    /**
     * Deletes a specified authentication module instance.
     * @param name Name of the authentication module instance going to be 
     *                deleted.
     * @throws AMConfigurationException if it fails to delete the 
     *         authentication instance.
     */
    public void deleteAuthenticationInstance(String name) 
        throws AMConfigurationException {
        AMAuthenticationInstance instance = getAuthenticationInstance(name);
        if (instance == null) {
            throw new AMConfigurationException(BUNDLE_NAME,
                "authInstanceNotExist", new Object[] {name});
        }

        if (isModuleInstanceInUse(name)) {
            throw new AMConfigurationException(BUNDLE_NAME,
                "authInstanceInUse", new Object[]{name});
        }
        String type = getAuthInstanceType(name);
        ServiceConfig serviceConfig = instance.getServiceConfig();
        if (serviceConfig == null) {
            throw new AMConfigurationException(BUNDLE_NAME,
                "authInstanceIsGlobal", new Object[] {type});
             }
        try {
            if (name.equals(type)) {
                // no subconfig
                Map attrs = serviceConfig.getAttributesWithoutDefaults();
                if (attrs != null) {
                    serviceConfig.removeAttributes(attrs.keySet());
                }
            } else {
                // remove sub config
                String serviceName = serviceConfig.getServiceName();
                ServiceConfigManager scm = new ServiceConfigManager(
                    serviceName, token);
                ServiceConfig orgConfig = scm.getOrganizationConfig(realm,null);
                orgConfig.removeSubConfig(name);
            }
            if (isInheritedAuthInstance(name)) {
                Set<String> moduleNames = new HashSet<String>();
                moduleNames.add(name);
                orgServiceConfig.removeAttributeValues(
                    ISAuthConstants.AUTH_ALLOWED_MODULES, moduleNames);
            }

            //In case of server mode AMAuthLevelManager will update AMAuthenticationManager about the change, and
            //there is no need to reinitialize the configuration twice. In client mode it is less likely that
            //AMAuthLevelManager listeners are in place, so let's reinitialize to be on the safe side.
            if (!SystemProperties.isServerMode()) {
                buildModuleInstanceForService(realm, serviceConfig.getServiceName());
            }
        } catch (Exception e) {
            throw new AMConfigurationException(e);
        }
    }

    /**
     * Returns <code>true</code> if this authentication module instance editable
     * by this user and/or in this realm.
     *
     * @param instance The authentication module instance.
     * @return <code>true</code> if editable.
     */
    public boolean isEditable(AMAuthenticationInstance instance) {
        return true;
    }

    private static String getServiceName(String module) {
        return MODULE_SERVICE_NAMES.get(module);
    }

    /**
     * Returns <code>true</code> if the module instance with the specified
     * name is being used by any named configurations or not.
     *
     * @param moduleInstance Name of the module instance.
     * @return <code>true</code> if the module instance in use.
     */ 
    private boolean isModuleInstanceInUse(String moduleInstance) {
        Set services = Collections.EMPTY_SET;
        boolean returnValue = false;

        try {
            ServiceConfigManager scm = new ServiceConfigManager(
                ISAuthConstants.AUTHCONFIG_SERVICE_NAME, token);
            ServiceConfig oConfig = scm.getOrganizationConfig(realm, null);

            if (oConfig != null) {
                ServiceConfig namedConfig = 
                    oConfig.getSubConfig("Configurations");
                if (namedConfig != null) { 
                        services = namedConfig.getSubConfigNames("*");
                }
            }
           } catch (Exception e) {
            if (DEBUG.messageEnabled()) {
                DEBUG.message("Failed to get named sub configurations.");
            }
        }

        for (Iterator it = services.iterator(); it.hasNext(); ) {
            String service = (String)it.next();
            if (DEBUG.messageEnabled()) {
                DEBUG.message("Checking " + service + " ...");
            }
            if (serviceContains(service, moduleInstance)) {
                if (DEBUG.messageEnabled()) {
                    DEBUG.message(moduleInstance + " is used in " + service);
                }
                returnValue = true;
                break;
            } 
        }
        return returnValue;
    }

    /**
     * Checks if the module instance name appears in the named configuration
     * definition.
     * @param serviceName String value for the name of the named configuration.
     * @param moduleInstance String value for the name of the module instance.
     * @return <code>true</code> if the module instance is in the service.
     */
    private boolean serviceContains(String serviceName, String moduleInstance) {
        boolean returnValue = false;
        Map dataMap = null;

        if (serviceName != null) {
            try {
                dataMap = AMAuthConfigUtils.getNamedConfig(serviceName,
                    realm, this.token);
            } catch (Exception e) {
                if (DEBUG.messageEnabled()) {
                    DEBUG.message("Failed to get named sub config attrs.");
                }
            }
        }

        if (dataMap != null) {
            Set xmlConfigValues = (Set)dataMap.get(AMAuthConfigUtils.ATTR_NAME);
            if (xmlConfigValues != null && !xmlConfigValues.isEmpty()) {
                String xmlConfig = (String)xmlConfigValues.iterator().next();
                if (DEBUG.messageEnabled()) {
                    DEBUG.message("service config for " + serviceName + "  = "
                        + xmlConfig);
                } 

                if (xmlConfig != null && xmlConfig.length() != 0) {
                        Document doc = XMLUtils.toDOMDocument(xmlConfig, DEBUG);
                    if (doc != null) {
                        Element vPair = doc.getDocumentElement();
                        Set values = XMLUtils.getAttributeValuePair(vPair);
                        for (Iterator it=values.iterator(); it.hasNext(); ) {
                            String value = (String)it.next();
                            String[] moduleInfo = value.split(" ");
                            if (moduleInfo.length > 0 && moduleInfo[0].equals(moduleInstance)) {
                                returnValue = true;
                                break;
                            }
                        }
                    }
                }
            }
        }
        return returnValue;
    }

    private static SSOToken getAdminToken() {
        return AccessController.doPrivileged(AdminTokenAction.getInstance());
    }
}
