/*
 * 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: AuthContext.java,v 1.25 2009/11/21 01:12:59 qcheng Exp $
 *
 * Portions Copyrighted 2010-2015 ForgeRock AS.
 */
package com.sun.identity.authentication;

import com.iplanet.am.util.SystemProperties;
import com.iplanet.dpro.session.SessionID;
import com.iplanet.dpro.session.service.SessionService;
import com.iplanet.services.comm.client.PLLClient;
import com.iplanet.services.comm.share.Request;
import com.iplanet.services.comm.share.RequestSet;
import com.iplanet.services.comm.share.Response;
import com.iplanet.services.naming.WebtopNaming;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOTokenManager;
import com.sun.identity.authentication.client.AuthClientUtils;
import com.sun.identity.authentication.server.AuthContextLocal;
import com.sun.identity.authentication.service.AMAuthErrorCode;
import com.sun.identity.authentication.service.AuthException;
import com.sun.identity.authentication.service.LoginState;
import com.sun.identity.authentication.share.AuthXMLTags;
import com.sun.identity.authentication.share.AuthXMLUtils;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.security.AMSecurityPropertiesException;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.locale.L10NMessageImpl;
import com.sun.identity.shared.xml.XMLUtils;
import org.forgerock.guice.core.InjectorHolder;
import org.forgerock.openam.authentication.service.protocol.RemoteHttpServletRequest;
import org.forgerock.openam.authentication.service.protocol.RemoteHttpServletResponse;
import org.forgerock.openam.session.SessionCache;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLStreamHandler;
import java.security.AccessController;
import java.security.KeyStore;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.Vector;

/**
 * The <code>AuthContext</code> provides the implementation for
 * authenticating users.
 * <p>
 * A typical caller instantiates this class and starts the login process.
 * The caller then obtains an array of <code>Callback</code> objects,
 * which contains the information required by the authentication plug-in
 * module. The caller requests information from the user. On receiving
 * the information from the user, the caller submits the same to this class.
 * While more information is required, the above process continues until all
 * the information required by the plug-ins/authentication modules, has
 * been supplied. The caller then checks if the user has successfully
 * been authenticated. If successfully authenticated, the caller can
 * then get the <code>Subject</code> and <code>SSOToken</code> for the user;
 * if not successfully authenticated, the caller obtains the
 * <code>AuthLoginException</code>.
 * <p>
 * The implementation supports authenticating users either locally
 * i.e., in process with all authentication modules configured or remotely
 * to an authentication service/framework. (See documentation to configure
 * in either of the modes).
 *
 * @supported.api
 */
public class AuthContext extends Object implements java.io.Serializable {

    private java.util.Locale clientLocale = null;
    
    private String server_proto =
        SystemProperties.get(Constants.AM_SERVER_PROTOCOL);
    private String server_host  =
        SystemProperties.get(Constants.AM_SERVER_HOST);
    private String server_port  =
        SystemProperties.get(Constants.AM_SERVER_PORT);
    private String server_uri  =
        SystemProperties.get(Constants.AM_SERVICES_DEPLOYMENT_DESCRIPTOR);
    private boolean includeReqRes  =
        SystemProperties.getAsBoolean(Constants.REMOTEAUTH_INCLUDE_REQRES);

    private static final String amAuthContext = "amAuthContext";
    
    private static final String JSS_PASSWORD_UTIL =
        "com.sun.identity.authentication.util.JSSPasswordUtil";
    
    private static final String JSSE_PASSWORD_CALLBACK =
        "com.sun.identity.security.keystore.AMCallbackHandler";
    
    static String protHandlerPkg =
        System.getProperty(Constants.PROTOCOL_HANDLER, Constants.JSSE_HANDLER);
    
    static boolean usingJSSEHandler =
        protHandlerPkg.equals(Constants.JSSE_HANDLER);
    
    // Debug & I18N class
    protected static Debug authDebug = Debug.getInstance(amAuthContext);
    protected static ResourceBundle bundle =
            com.sun.identity.shared.locale.Locale.getInstallResourceBundle(amAuthContext);
    
    Status loginStatus = Status.IN_PROGRESS;
    String organizationName = "";
    Document receivedDocument;
    AuthLoginException loginException = null;

    String hostName = null;
    private boolean forceAuth = false;
    private boolean localSessionChecked = false;
    String nickName = null;
    private URL authURL = null;
    private URL authServiceURL = null;
    private SSOToken ssoToken = null;
    private String ssoTokenID = null;
    private static SSOToken appSSOToken = null;
    com.sun.identity.authentication.server.AuthContextLocal acLocal = null;
    private final static int DEFAULT_RETRY_COUNT = 1;
    private int retryRunLogin = DEFAULT_RETRY_COUNT;
    
    /**
     * Variables for checking auth service is running local
     */ 
    public boolean localFlag = false;
    /**
     * Variables for local AuthService identifier
     */ 
    public static String localAuthServiceID;

    // Variable to check if 6.3 style remote AuthN has to be performed
    static boolean useOldStyleRemoteAuthentication;
    static boolean useNewStyleRemoteAuthentication;
    
    // this cookieTable is used to keep all the cookies retrieved from the
    // the PLL layer and replay them in subsequent auth requests, mainly for
    // persistence purpose.
    private HashMap cookieTable = new HashMap();

    private HttpServletRequest remoteRequest = null;
    private HttpServletResponse remoteResponse = null;

    private final SessionCache sessionCache = SessionCache.getInstance();
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name or sub organization name. This organization or
     * sub-organization name must be either "/" separated
     * ( where it starts with "/" ) , DN , Domain name or DNS Alias Name.
     * <p>
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param orgName Name of the user's organization.
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     * 
     * @supported.api
     */
    public AuthContext(String orgName) throws AuthLoginException {
        organizationName = orgName;
    }
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name, or sub organization name and the OpenAM server
     * URL.
     * This organization or sub-organization name must be either "/" separated
     * ( where it starts with "/" ) , DN , Domain name or DNS Alias Name.
     * And the <code>url</code> should specify the OpenAM server's protocol,
     * host name, and port number,
     * for example : <code>http://daye.red.iplanet.com:58080</code>
     *
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param orgName name of the user's organization
     * @param url URL of the OpenAm instance to talk to
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     *
     * @supported.api
     */
    public AuthContext(String orgName, URL url) throws AuthLoginException {
        organizationName = orgName;
        authURL = url;
    }
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name, or sub organization name and a nick name
     * for the certificate to be used in SSL handshake if client authentication
     * is turn on in the server side.
     * This organization or sub-organization name must be either "/" separated
     * ( where it starts with "/" ) , DN , Domain name or DNS Alias Name.
     *
     * This constructor would be mainly used for the Certificate based
     * authentication. If the certificate database contains multiple matching
     * certificates for SSL, this constructor must be called in order for the
     * desired certificate to be used for the Certificate based authentication.
     *
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param orgName name of the user's organization
     * @param nickName nick name for the certificate to be used
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     *
     * @supported.api
     */
    public AuthContext(String orgName, String nickName)
            throws AuthLoginException {
        organizationName = orgName;
        this.nickName = nickName;
    }
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name, or sub organization name, a nick name
     * for the certificate to be used in SSL handshake if client authentication
     * is enabled on the server side and the OpenAM URL.
     * This organization or sub-organization name must be either "/" separated
     * ( where it starts with "/" ) ,  DN , Domain name or a DNS Alias Name.
     * And the <code>url</code> should specify the OpenAM server's protocol,
     * host name, and port number,
     * for example : <code>http://daye.red.iplanet.com:58080</code>
     * This constructor would be mainly used for the Certificate based
     * authentication. If the certificate database contains multiple matching
     * certificates for SSL, this constructor must be called in order for the
     * desired certificate to be used for the Certificate based authentication.
     *
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param orgName name of the user's organization
     * @param nickName nick name for the certificate to be used
     * @param url URL of the OpenAM server to talk to
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     *
     * @supported.api
     */
    public AuthContext(String orgName, String nickName, URL url)
            throws AuthLoginException {
        organizationName = orgName;
        this.nickName = nickName;
        authURL = url;
    }
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name, or sub organization name contained in the
     * single sign on token.
     *
     * This constructor should be called for re-authentication of an
     * authenticated user. single sign on token is the authenticated resource's
     * Single-Sign-On Token. If the session properties based on
     * the login method used matches those in the user's new
     * authenticated  session then session upgrade will be done.
     * A new session containing properties from both old single sign on token
     * and new session shall be returned and old session will be
     * destroyed if authentication  passes.
     *
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param ssoToken single sign on token representing the resource's previous
     *        authenticated session.
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     *
     * @supported.api
     */
    public AuthContext(SSOToken ssoToken) throws AuthLoginException {
        this.ssoToken = ssoToken;
    }
    
    /**
     * Constructs an instance of <code>AuthContext</code> for a given
     * organization name, or sub organization name contained in the
     * single sign on token.
     *
     * This constructor should be called for re-authentication of an
     * authenticated user. single sign on token is the authenticated resource's
     * Single-Sign-On Token. If the session properties based on
     * the login method used matches those in the user's new
     * authenticated  session then session upgrade will be done.
     * If forceAuth flag is <code>true</code> then the existing session 
     * is used and no new session is created otherwise this constructor 
     * behaves same as the constructor with no forceAuth flag.
     *
     * Caller would then use <code>login</code> to start the
     * authentication process and use <code>getRequirements()</code> and
     * <code>submitRequirements()</code> to pass the credentials
     * needed for authentication by the plugin authentication modules.
     * The method <code>getStatus()</code> returns the
     * authentication status.
     *
     * @param ssoToken single sign on token representing the resource's 
     *        previous authenticated session.
     * @param forceAuth indicates that authentication preocess has to be 
     *        restarted and given single sign on token will be used and new 
     *        session will not be created.
     * @throws AuthLoginException if <code>AuthContext</code> creation fails.
     *         This exception is kept for backward compatibility only.
     *
     * @supported.api
     */
    public AuthContext(SSOToken ssoToken, boolean forceAuth) throws 
        AuthLoginException {
        this.ssoToken = ssoToken;
        this.forceAuth = forceAuth;
    }
    
    /**
     * Starts the login process for the given <code>AuthContext</code> object.
     *
     * @exception AuthLoginException if an error occurred during login.
     *
     * @supported.api
     */
    public void login() throws AuthLoginException {
        login(null, null, null, null, null, null);
    }

    /**
     * Starts the login process for the given <code>AuthContext</code> object.
     *
     * @param request The HttpServletRequest that was sent to start the authentication process.
     * @param response The corresponding HttpServletResponse for the HttpServletRequest.
     * @throws AuthLoginException If an error occurred during login.
     *
     * @supported.api
     */
    public void login(HttpServletRequest request, HttpServletResponse response) throws AuthLoginException {
        login(null, null, null, null, request, response);
    }
    
    /**
     * Starts the login process for the given <code>AuthContext</code> object
     * identified by the index type and index name. The <code>IndexType</code>
     * defines the possible kinds of "objects" or "resources" for which an
     * authentication can be performed. Currently supported index types are
     * users, roles, services (or application), levels, resources and
     * mechanism/authentication modules.
     *
     * @param type Authentication index type.
     * @param indexName Authentication index name.
     * @exception AuthLoginException if an error occurred during login.
     *
     * @supported.api
     */
    public void login(IndexType type, String indexName)
            throws AuthLoginException {
        login(type, indexName, null, null, null, null);
    }
    
    /**
     * Starts the login process for the given <code>AuthContext</code> object
     * identified by the index type and index name.
     * The <code>IndexType</code> defines the possible kinds of "objects"
     * or "resources" for which an authentication can
     * be performed. Currently supported index types are
     * users, roles, services (or application), levels, resources and mechanism.
     * It allows the caller to pass in the desired locale for this request.
     *
     * @param type authentication index type
     * @param indexName authentication index name
     * @param locale locale setting
     *
     * @exception AuthLoginException if an error occurred during login
     */
    public void login(IndexType type, String indexName, String locale)
            throws AuthLoginException {
        login(type, indexName, null, null, locale);
    }

    /**
     * Starts the login process for the given <code>AuthContext</code> object
     * identified by the index type and index name and also completes
     * the login process by submitting the given User credentials
     * in the form of Callbacks.
     * The <code>IndexType</code> defines the possible kinds of "objects"
     * or "resources" for which an authentication can
     * be performed. Currently supported index types are
     * users, roles, services (or application), levels, resources and mechanism.
     * <p>
     * NOTE : This is a simplified wrapper method to eliminate multi-step calls
     * to 'login' and submit credentials. This method is useful and will work
     * only for those authentication modules which require only one set of
     * callbacks or one page. This method can not be used to authenticate to
     * authentication modules which require user interaction or multiple pages.
     *
     * @param type Authentication index type.
     * @param indexName Authentication index name.
     * @param userInfo User information/credentials in the form of array of
     *        <code>Callback</code> objects. The <code>Callback</code> objects
     *        array must be in the same order as defined in the authentication
     *        module properties file, otherwise authentication module code will
     *        not work.
     * @return single-sign-on token for the valid user after successful
     *         authentication.
     * @exception AuthLoginException if an error occurred during login.
     */
    public SSOToken login(IndexType type, String indexName, Callback[] userInfo)
            throws AuthLoginException {
        login(type, indexName, null, null, null, null);
        
        SSOToken ssoToken = null;
        Callback[] callbacks = null;
        
        while (hasMoreRequirements()) {
            callbacks = getRequirements();
            
            if (callbacks != null) {
                try {
                    submitRequirements(userInfo);
                } catch (Exception e) {
                    if (authDebug.messageEnabled()) {
                        authDebug.message(
                            "Error: submitRequirements with userInfo : "
                        + e.getMessage());
                    }
                    throw new AuthLoginException(e);
                }
            }
        }
        try {
            if (getStatus() == AuthContext.Status.SUCCESS) {
                ssoToken = getSSOToken();
            }
        } catch (Exception e) {
            if (authDebug.messageEnabled()) {
                authDebug.message("Error: getSSOToken : " + e.getMessage());
            }
            throw new AuthLoginException(e);
        }
        return ssoToken;
    }
    
    /**
     * Starts the login process for the given <code>AuthContext</code> object
     * identified by the index type and index name with default parameters.
     * The <code>IndexType</code> defines the possible kinds of "objects"
     * or "resources" for which an authentication can be performed. Currently
     * supported index types are users, roles, services (or application),
     * levels, resources and mechanism/authentication modules.
     *
     * @param indexType authentication index type.
     * @param indexName authentication index name.
     * @param params contains the default values for the callbacks. The order
     *        of this array matches the callbacks order for this login process.
     *        value for the <code>PasswordCallback</code> is also in String
     *        format, it will be converted to <code>char[]</code> when it is
     *        set to the callback. Internal processing for this string array
     *        uses <code>|</code> as separator. Hence <code>|</code> should not
     *        be used in these default values. Currently only
     *        <code>NameCallback</code> and <code>PasswordCallback</code> are
     *        supported.
     * @exception AuthLoginException if an error occurred during login.
     *
     * @supported.api
     */
    public void login(IndexType indexType, String indexName, String[] params)
            throws AuthLoginException {
        login(indexType, indexName, params, null, null, null);
    }

    public void login(IndexType indexType,
                      String indexName,
                      String[] params,
                      HttpServletRequest request,
                      HttpServletResponse response)
            throws AuthLoginException {
        login(indexType, indexName, params, null, request, response);
    }
    
    /**
     * Starts the login process for the given <code>AuthContext</code> object
     * identified by the index type and index name with certain parameters
     * and environment map.
     * The <code>IndexType</code> defines the possible kinds of "objects"
     * or "resources" for which an authentication can be performed. Currently
     * supported index types are users, roles, services (or application),
     * levels, modules and resources.
     *
     * @param indexType authentication index type.
     * @param indexName authentication index name.
     * @param params contains the default values for the callbacks. The order
     *        of this array matches the callbacks order for this login process.
     *        value for the <code>PasswordCallback</code> is also in String
     *        format, it will be converted to <code>char[]</code> when it is
     *        set to the callback. Internal processing for this string array
     *        uses <code>|</code> as separator. Hence <code>|</code> should not
     *        be used in these default values. Currently only
     *        <code>NameCallback</code> and <code>PasswordCallback</code> are
     *        supported.
     * @param envMap contains the environment key/value pairs. Key is a String
     *        object indicating the property name, value is a Set of String
     *        values for the property. Currenty this parameter only applicable
     *        when the indexTye is <code>AuthContext.IndexType.RESOURCE</code>.
     * @exception AuthLoginException if an error occurred during login.
     *
     * @supported.api
     */
    public void login(IndexType indexType, String indexName, 
        String[] params, Map envMap)
            throws AuthLoginException {
        login(indexType, indexName, params, envMap, null, null);
    }
    
    public void login(
        IndexType indexType,
        String indexName,
        String[] params,
        Map envMap,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws AuthLoginException {
        if (clientLocale == null) {
            login(indexType, indexName, params, envMap, null, request, response);
        } else {
            String localeStr = clientLocale.toString();
            login(indexType, indexName, params, envMap, localeStr, request, response);
        }
    }

    private void login(
        IndexType indexType,
        String indexName,
        String[] params,
        Map envMap,
        String locale
    ) throws AuthLoginException {
        login(indexType, indexName, params, envMap, locale, null, null);
    }

    private void login(
        IndexType indexType,
        String indexName,
        String[] params,
        Map envMap,
        String locale,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws AuthLoginException {
        if (ssoToken != null) {
            try {
                organizationName = ssoToken.getProperty(
                    ISAuthConstants.ORGANIZATION);
                ssoTokenID = ssoToken.getTokenID().toString();
                authURL = sessionCache.getSession(new SessionID(ssoTokenID)).getSessionServiceURL();
            } catch (Exception e) {
                throw new AuthLoginException(e);
            }
        }
        
        if (authURL != null) {
            authServiceURL = getAuthServiceURL(authURL.getProtocol(),
                authURL.getHost(), Integer.toString(authURL.getPort()),
                authURL.getPath());
        }
        
        AuthLoginException authException = null;
        try {
            if (authServiceURL == null) {
                authServiceURL = getAuthServiceURL( server_proto,
                    server_host, server_port, server_uri);
            }
            if (authServiceURL != null) {
            	if (authDebug.messageEnabled()) {
            	    authDebug.message("AuthContext.login : runLogin against "
            		    + authServiceURL);
            	}
                runLogin(indexType, indexName, params, envMap, locale,
                        request, response);
                return;
            }
        } catch (AuthLoginException e) {
            authException = e;
            authDebug.error("Failed to login to " + authServiceURL);
        } catch (Exception e) {
            authDebug.error("Failed to login to " + authServiceURL
                + ": " + e.getMessage(),e);
        }
        
        if (authURL == null) {
            // failover when authURL is not specified
            Vector serviceURLs = null;
            try {
                serviceURLs = WebtopNaming.getServiceAllURLs(
                AuthXMLTags.AUTH_SERVICE);
            } catch (Exception e) {
                throw new AuthLoginException(amAuthContext, "loginError",
                new Object[]{e.getMessage()});
            }
            
            if (authDebug.messageEnabled()) {
                authDebug.message("Org Name : " + organizationName);
                authDebug.message("ssoTokenID: " + ssoTokenID);
                authDebug.message("serviceURLs: " + serviceURLs);
            }
            
            if (serviceURLs != null) {
                serviceURLs.remove(authServiceURL);
                for (Enumeration e = serviceURLs.elements();
                e.hasMoreElements(); ) {
                    authServiceURL = (URL)e.nextElement();
                    try {
                        runLogin(indexType, indexName, params,
                            envMap, locale, request, response);
                        return;
                    } catch (AuthLoginException ex) {
                        authException = ex;
                        authDebug.error("Failed to login in failover with " +
                        authServiceURL + ": " + ex.getMessage());
                    }
                }
            }
        }
        authDebug.error("Authentication failed.");
        if (authException != null) {
            throw authException;
        } else {
            throw new AuthLoginException(amAuthContext, "loginError",null);
        }
    }
    
    
    private void runLogin(
        IndexType indexType,
        String indexName,
        String[] params,
        Map envMap,
        String locale,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws AuthLoginException {
        if (!localFlag) {
            setLocalFlag(authServiceURL);
        }

        if (appSSOToken == null) {
            if (!((indexType == IndexType.MODULE_INSTANCE) && 
                (indexName.equals("Application")))){
                appSSOToken = getAppSSOToken(false);
            }
        }
        
        if (localFlag) {
            try {
                if (ssoTokenID == null) {
                    acLocal = com.sun.identity.authentication.service.AuthUtils.
                        getAuthContext(organizationName);
                } else {
                    if (authDebug.messageEnabled()) {
                        authDebug.message("AuthContext.runLogin: "
                        + "ForceAuth = "+forceAuth);
                    }
                    acLocal = com.sun.identity.authentication.service.AuthUtils.
                        getAuthContext(organizationName, ssoTokenID, false, 
                            null, null, null, forceAuth);
                }
                LoginState loginState = acLocal.getLoginState();
                /*
                 * Set both the HttpRequest and HttpResponse on the login state so they are accessible by the Auth
                 * Modules.
                 */
                if (request != null) {
                    loginState.setHttpServletRequest(request);
                    Hashtable hashtable = AuthClientUtils.parseRequestParameters(request);
                    loginState.setParamHash(hashtable);
                }
                if (response != null) {
                    loginState.setHttpServletResponse(response);
                }
                if (hostName != null) {
                    acLocal.getLoginState().setClient(hostName);
                }
                acLocal.login(indexType, indexName, envMap, locale);
            } catch (AuthException e) {
                throw new AuthLoginException(e);
            }
            if (acLocal.getStatus().equals(Status.SUCCESS)) {
                onSuccessLocal();
            }
            return;
        }
        
        // Check if 7.0 RR stype protocol needs to be used
        // This will setup NewAuthContext and authHandles
        if (useOldStyleRemoteAuthentication) {
            runRemoteOldAuthContext();
            if (loginException != null) {
                throw loginException;
            }
        }
        // Run Login
        runRemoteLogin(indexType, indexName, params, envMap, locale,
                request, response);
        // reset the retry count
        retryRunLogin = DEFAULT_RETRY_COUNT;
        
        if (authDebug.messageEnabled()) {
            authDebug.message("useNewStyleRemoteAuthentication : " 
                + useNewStyleRemoteAuthentication);
            authDebug.message("useOldStyleRemoteAuthentication : " 
                + useOldStyleRemoteAuthentication);
            authDebug.message("receivedDocument : " + receivedDocument);
            authDebug.message("loginException : " + loginException);
        }

        // If "Login" fails and we have not set 6.3, 7.0 RR style protocol
        // the server could be either 6.3 or 7.0 RR. Hence try "NewAuthContext"
        // and then "Login"
        if (!useNewStyleRemoteAuthentication &&
            !useOldStyleRemoteAuthentication &&
            (receivedDocument == null || 
            (getAuthenticationHandle(receivedDocument)).equals("null")) && 
            loginException != null) {
            if (authDebug.messageEnabled()) {
                authDebug.message("AuthContext: trying 6.3 style remote " +
                    "AuthN and setting the flag to use 6.3 style");
            }
            useOldStyleRemoteAuthentication = true;
            // Server could be either 6.3 or 7.0 RR, try old style
            // Construct the Request XML with New AuthContext parameters
            loginException = null;  // Reset loginException
            runRemoteOldAuthContext();
            if (loginException != null) {
                throw loginException;
            }
            // Re-try login process with AuthIdentifier
            runRemoteLogin(indexType, indexName, params,
                envMap, locale, request, response);
            // reset the retry count
            retryRunLogin = DEFAULT_RETRY_COUNT;
        } else if (!useNewStyleRemoteAuthentication) {
            useNewStyleRemoteAuthentication = true;
        }
        if (loginException != null) {
            throw loginException;
        }
    }

    private void runRemoteLogin(IndexType indexType, String indexName, String[] params, Map envMap,
            String locale, HttpServletRequest req, HttpServletResponse res) throws AuthLoginException {
        try {
            String xmlString;
            // remote auth
            StringBuilder request = new StringBuilder(100);
            String authHandle = getAuthHandle();
            if (ssoTokenID != null && "0".equals(authHandle)) {
                if (authDebug.messageEnabled()) {
                    authDebug.message("AuthContext.runRemoteLogin: Found SSOTokenID " + ssoTokenID);
                }
                authHandle = ssoTokenID;
            }

            request.append(MessageFormat.format(AuthXMLTags.XML_REQUEST_PREFIX, authHandle));
            if (appSSOToken != null) {
                request.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                request.append(appSSOToken.getTokenID().toString());
                request.append(AuthXMLTags.APPSSOTOKEN_END);
            }
            request.append(AuthXMLTags.LOGIN_BEGIN);

            if (!useOldStyleRemoteAuthentication) {
                request.append(AuthXMLTags.SPACE)
                    .append(AuthXMLTags.ORG_NAME_ATTR)
                    .append(AuthXMLTags.EQUAL)
                    .append(AuthXMLTags.QUOTE)
                    .append(XMLUtils.escapeSpecialCharacters(organizationName))
                    .append(AuthXMLTags.QUOTE);
                if (hostName != null) {
                    request.append(AuthXMLTags.SPACE)
                    .append(AuthXMLTags.HOST_NAME_ATTR)
                    .append(AuthXMLTags.EQUAL)
                    .append(AuthXMLTags.QUOTE)
                    .append(XMLUtils.escapeSpecialCharacters(hostName))
                    .append(AuthXMLTags.QUOTE);
                }
                if (locale != null && !locale.isEmpty()) {
                        request.append(AuthXMLTags.SPACE)
                        .append(AuthXMLTags.LOCALE)
                        .append(AuthXMLTags.EQUAL)
                        .append(AuthXMLTags.QUOTE)
                        .append(XMLUtils.escapeSpecialCharacters(locale))
                        .append(AuthXMLTags.QUOTE);
                }
                if (forceAuth) {
                    request.append(AuthXMLTags.SPACE)
                    .append(AuthXMLTags.FORCE_AUTH_ATTR)
                    .append(AuthXMLTags.EQUAL)
                    .append(AuthXMLTags.QUOTE)
                    .append("true")
                    .append(AuthXMLTags.QUOTE);
                }
            }
            request.append(AuthXMLTags.ELEMENT_END);

            if (indexType != null) {
                request.append(AuthXMLTags.INDEX_TYPE_PAIR_BEGIN)
                    .append(AuthXMLTags.SPACE)
                    .append(AuthXMLTags.INDEX_TYPE)
                    .append(AuthXMLTags.EQUAL)
                    .append(AuthXMLTags.QUOTE);

                if (indexType == IndexType.USER) {
                    request.append(AuthXMLTags.INDEX_TYPE_USER_ATTR);
                } else if (indexType == IndexType.ROLE) {
                    request.append(AuthXMLTags.INDEX_TYPE_ROLE_ATTR);
                } else if (indexType == IndexType.SERVICE) {
                    request.append(AuthXMLTags.INDEX_TYPE_SVC_ATTR);
                } else if (indexType == IndexType.MODULE_INSTANCE) {
                    request.append(AuthXMLTags.INDEX_TYPE_MODULE_ATTR);
                } else if (indexType == IndexType.LEVEL) {
                    request.append(AuthXMLTags.INDEX_TYPE_LEVEL_ATTR);
                } else if (indexType == IndexType.COMPOSITE_ADVICE) {
                    request.append(AuthXMLTags.INDEX_TYPE_COMPOSITE_ADVICE_ATTR);
                } else if (indexType == IndexType.RESOURCE) {
                    request.append(AuthXMLTags.INDEX_TYPE_RESOURCE);
                }
                request.append(AuthXMLTags.QUOTE)
                    .append(AuthXMLTags.ELEMENT_END)
                    .append(AuthXMLTags.INDEX_NAME_BEGIN)
                    .append(XMLUtils.escapeSpecialCharacters(indexName))
                    .append(AuthXMLTags.INDEX_NAME_END)
                    .append(AuthXMLTags.INDEX_TYPE_PAIR_END);
            }

            if (locale != null && locale.length() > 0) {
                request.append(AuthXMLTags.LOCALE_BEGIN);
                request.append(XMLUtils.escapeSpecialCharacters(locale));
                request.append(AuthXMLTags.LOCALE_END);
            }

            if (params != null) {
                StringBuilder paramString = new StringBuilder();
                for (int i = 0; i < params.length; i++) {
                    if (i != 0 ) {
                        paramString.append(ISAuthConstants.PIPE_SEPARATOR);
                    }
                    paramString.append(XMLUtils.escapeSpecialCharacters(params[i]));
                }
                request.append(AuthXMLTags.PARAMS_BEGIN)
                    .append(paramString.toString())
                    .append(AuthXMLTags.PARAMS_END);
            }
            if (envMap != null && !envMap.isEmpty()) {
                StringBuilder envString = new StringBuilder();
                for (Map.Entry<String, Set<String>> entry : ((Map<String, Set<String>>) envMap).entrySet()) {
                    // convert Map to XMLString as follows:
                    // <EnvValue>keyname|value1|value2|...</EnvValue>
                    String keyName = entry.getKey();
                    Set<String> values = entry.getValue();
                    if (values != null && !values.isEmpty()) {
                        envString.append(AuthXMLTags.ENV_AV_BEGIN)
                                .append(AuthClientUtils.escapePipe(XMLUtils.escapeSpecialCharacters(keyName)));
                        for (String value : values) {
                            envString.append(ISAuthConstants.PIPE_SEPARATOR)
                                    .append(AuthClientUtils.escapePipe(XMLUtils.escapeSpecialCharacters(value)));
                        }
                        envString.append(AuthXMLTags.ENV_AV_END);
                    }
                }
                request.append(AuthXMLTags.ENV_BEGIN)
                    .append(envString.toString())
                    .append(AuthXMLTags.ENV_END);
            }
            request.append(AuthXMLTags.LOGIN_END);

            if (includeReqRes) {
                request.append(AuthXMLTags.REMOTE_REQUEST_RESPONSE_START)
                .append(AuthXMLTags.HTTP_SERVLET_REQUEST_START);
                String encObj = "";

                if (req != null) {
                    try {
                        encObj = AuthXMLUtils.serializeToString(new RemoteHttpServletRequest(req));
                    } catch (IOException ioe) {
                        authDebug.error("AuthXMLUtils::runRemoteLogin Unable to serailize http request", ioe);
                    }

                    if (authDebug.messageEnabled()) {
                        authDebug.message("req=" + new RemoteHttpServletRequest(req).toString());
                    }

                    request.append(encObj);
                }

                request.append(AuthXMLTags.HTTP_SERVLET_REQUEST_END);
                request.append(AuthXMLTags.HTTP_SERVLET_RESPONSE_START);

                if (res != null) {
                    encObj = "";

                    try {
                        encObj = AuthXMLUtils.serializeToString(new RemoteHttpServletResponse(res));
                    } catch (IOException ioe) {
                        authDebug.error("AuthXMLUtils::runRemoteLogin Unable to serailize http response", ioe);
                    }

                    if (authDebug.messageEnabled()) {
                        authDebug.message("res=" + res);
                    }

                    request.append(encObj);
                }

                request.append(AuthXMLTags.HTTP_SERVLET_RESPONSE_END)
                .append(AuthXMLTags.REMOTE_REQUEST_RESPONSE_END);
            } else {
                if (authDebug.messageEnabled()) {
                    authDebug.message("Not including req/res " + includeReqRes);
                }
            }

            request.append(AuthXMLTags.XML_REQUEST_SUFFIX);
            xmlString = request.toString();

            // process the request, which will check for exceptions
            // and also get the authentication handle ID
            receivedDocument = processRequest(xmlString);

            // Check set the login status
            checkAndSetLoginStatus();

            // if the app token was refreshed, retry remote login
            if (loginException != null &&
                loginException.getErrorCode().equals(AMAuthErrorCode.REMOTE_AUTH_INVALID_SSO_TOKEN) &&
                retryRunLogin > 0) {
                retryRunLogin--;

                if (authDebug.messageEnabled()) {
                    authDebug.message("Run remote login failed due to expired app token, retying");
                }

                // reset as we are starting again
                loginStatus = Status.IN_PROGRESS;
                runRemoteLogin(indexType, indexName, params, envMap, locale, req,  res);
            }
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }

    private void runRemoteOldAuthContext() throws AuthLoginException {
        try {
            StringBuilder request = new StringBuilder(100);
            String[] objs = { "0" };
            if (ssoTokenID != null) {
                objs[0] = ssoTokenID;
            }
            request.append(MessageFormat.format(
                AuthXMLTags.XML_REQUEST_PREFIX, (Object[])objs))
                .append(AuthXMLTags.NEW_AUTHCONTEXT_BEGIN)
                .append(AuthXMLTags.SPACE)
                .append(AuthXMLTags.ORG_NAME_ATTR)
                .append(AuthXMLTags.EQUAL)
                .append(AuthXMLTags.QUOTE)
                .append(XMLUtils.escapeSpecialCharacters(organizationName))
                .append(AuthXMLTags.QUOTE)
                .append(AuthXMLTags.ELEMENT_END)
                .append(AuthXMLTags.NEW_AUTHCONTEXT_END)
                .append(AuthXMLTags.XML_REQUEST_SUFFIX);
            // process the request, which will check for exceptions
            // and also get the authentication handle ID
            receivedDocument = processRequest(request.toString());

            // Check set the login status
            checkAndSetLoginStatus();
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }
    
    /**
     * Returns the set of Principals or Subject the user has been
     * authenticated as.
     * This should be invoked only after successful authentication.
     *
     * @return <code>Subject</code> for the authenticated User.
     *         If the authentication fails or the authentication is in process,
     *         this will return <code>null</code>.
     *
     * @supported.api
     */
    public Subject getSubject() {
        if (localFlag) {
            if (!acLocal.getStatus().equals(Status.SUCCESS)) {
                return (null);
            }
            return (acLocal.getSubject());
        } else {
            if (!loginStatus.equals(Status.SUCCESS)) {
                return (null);
            }
            return (getSubject(receivedDocument));
        }
    }

   /**
    * Returns a <code>Map</code> object that
    * that contains cookies set by AM server
    *
    * @return a <code>Map</code> of cookie name and
    * <code>Cookie</code> object.
    */
    public Map getCookieTable() {
        return cookieTable;
    }
    
    /**
     * Returns <code>true</code> if the login process requires more
     * information from the user to complete the authentication.
     * <p>
     * NOTE: This method has to be called as a condition of a
     * <code>while</code> loop in order to complete the authentication process
     * and get the correct <code>Status</code> after submitting the
     * requirements.
     *
     * @return <code>true</code> if more credentials are required from the user.
     *
     * @supported.api
     */
    public boolean hasMoreRequirements() {
        if (localFlag) {
            return (acLocal.hasMoreRequirements(false));
        } else {
            if ((!loginStatus.equals(Status.IN_PROGRESS)) ||
            ((getCallbacks(receivedDocument, false)) == null)) {
                return (false);
            }
            return (true);
        }
    }
    
    /**
     * Returns <code>true</code> if the login process requires more information
     * from the user to complete the authentication.
     *
     * NOTE: This method has to be called as a condition of a <ode>while</code>
     * loop in order to complete the authentication process and get the correct
     * <code>Status</code> after submitting the requirements.
     *
     * @param noFilter flag indicates whether to filter
     *        <code>PagePropertiesCallback</code> or not. Value
     *        <code>true</code> will not filter
     *        <code>PagePropertiesCallback</code>.
     * @return <code>true</code> if more credentials are required from the user.
     *
     * @supported.api
     */
    public boolean hasMoreRequirements(boolean noFilter) {
        if (localFlag) {
            return (acLocal.hasMoreRequirements(noFilter));
        } else {
            if ((!loginStatus.equals(Status.IN_PROGRESS)) ||
            ((getCallbacks(receivedDocument, noFilter)) == null)) {
                return (false);
            }
            return (true);
        }
    }
    
    /**
     * Returns an array of <code>Callback</code> objects that must be populated
     * by the user and returned back. These objects are requested by the
     * authentication plug-ins, and these are usually displayed to the user.
     * The user then provides the requested information for it to be
     * authenticated.
     *
     * @return an array of <code>Callback</code> objects requesting credentials
     *         from user
     *
     * @supported.api
     */
    public Callback[] getRequirements() {
        if (localFlag) {
            if (!acLocal.getStatus().equals(Status.IN_PROGRESS)) {
                return (null);
            }
            return (acLocal.getRequirements(false));
        } else {
            if (!loginStatus.equals(Status.IN_PROGRESS)) {
                return (null);
            }
            return (getCallbacks(receivedDocument, false));
        }
    }
    
    /**
     * Returns an array of <code>Callback</code> objects that
     * must be populated by the user and returned back.
     * These objects are requested by the authentication plug-ins,
     * and these are usually displayed to the user. The user then provides
     * the requested information for it to be authenticated.
     *
     * @param noFilter boolean flag indicating whether to filter
     * <code>PagePropertiesCallback</code> or not. Value <code>true</code> will
     * not filter <code>PagePropertiesCallback</code>.
     *
     * @return an array of <code>Callback</code> objects requesting credentials
     * from user
     *
     * @supported.api
     */
    public Callback[] getRequirements(boolean noFilter) {
        if (localFlag) {
            if (!acLocal.getStatus().equals(Status.IN_PROGRESS)) {
                return (null);
            }
            return (acLocal.getRequirements(noFilter));
        } else {
            if (!loginStatus.equals(Status.IN_PROGRESS)) {
                return (null);
            }
            return (getCallbacks(receivedDocument, noFilter));
        }
    }

    /**
     * Fetches the remote request from the context
     *
     * @return The Http Servlet Request
     */
    public HttpServletRequest getRemoteRequest() {
        return remoteRequest;
    }

    /**
     * Fetches the remote response from the context
     *
     * @return The Http Servlet Response
     */
    public HttpServletResponse getRemoteResponse() {
        return remoteResponse;
    }
    
    /**
     * Submits the populated <code>Callback</code> objects to the
     * authentication plug-in modules. Called after <code>getRequirements</code>
     * method and obtaining user's response to these requests.
     *
     * @param info Array of <code>Callback</code> objects.
     *
     * @supported.api
     */
    public void submitRequirements(Callback[] info) {
        submitRequirements(info, null, null);
    }

    public void submitRequirements(Callback[] info, HttpServletRequest request,
            HttpServletResponse response) {
        if (authDebug.messageEnabled()) {
            authDebug.message("submitRequirements with Callbacks : " + info);
        }
        
        if (localFlag) {
            // Check if we are still in login session
            if (!acLocal.getStatus().equals(Status.IN_PROGRESS)) {
                return;
            }
            acLocal.submitRequirements(info);
            if (acLocal.getStatus().equals(Status.SUCCESS)) {
                onSuccessLocal();
            }
            return;
        } else {
            // Check if we are still in login session
            if (!loginStatus.equals(Status.IN_PROGRESS)) {
                return;
            }
            
            // Construct the XML
            try {
                StringBuilder xml = new StringBuilder(100);
                String[] authHandles = new String[1];
                authHandles[0] = getAuthenticationHandle(receivedDocument);
                xml.append(MessageFormat.format(
                    AuthXMLTags.XML_REQUEST_PREFIX,(Object[])authHandles));
                if (appSSOToken != null) {
                    xml.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                    xml.append(appSSOToken.getTokenID().toString()).
                        append(AuthXMLTags.APPSSOTOKEN_END);
                }
                xml.append(AuthXMLTags.SUBMIT_REQS_BEGIN)
                .append(AuthXMLUtils.getXMLForCallbacks(info));

                if (clientLocale != null) {
                    String localeStr = clientLocale.toString();
                    if ((localeStr != null) && (localeStr.length() > 0)) {
                        xml.append(AuthXMLTags.LOCALE_BEGIN)
                        .append(XMLUtils.escapeSpecialCharacters(localeStr))
                        .append(AuthXMLTags.LOCALE_END);
                    }
                }

                xml.append(AuthXMLTags.SUBMIT_REQS_END);

                if (includeReqRes) {
                    // serialized request and response objects
                    xml.append(AuthXMLTags.REMOTE_REQUEST_RESPONSE_START)
                    .append(AuthXMLTags.HTTP_SERVLET_REQUEST_START);
                    String encObj = "";

                    if (request != null) {
                        try {
                            encObj = AuthXMLUtils.serializeToString(new RemoteHttpServletRequest(request));
                        } catch (IOException ioe) {
                            authDebug.error("AuthXMLUtils::runRemoteLogin Unable to serailize http request", ioe);
                        }

                        if (authDebug.messageEnabled()) {
                            authDebug.message("req=" + request);
                        }

                        xml.append(encObj);
                    }

                    xml.append(AuthXMLTags.HTTP_SERVLET_REQUEST_END);
                    xml.append(AuthXMLTags.HTTP_SERVLET_RESPONSE_START);

                    if (response != null) {
                        encObj = "";

                        try {
                            encObj = AuthXMLUtils.serializeToString(new RemoteHttpServletResponse(response));
                        } catch (IOException ioe) {
                            authDebug.error("AuthXMLUtils::runRemoteLogin Unable to serailize http response", ioe);
                        }

                        if (authDebug.messageEnabled()) {
                            authDebug.message("res=" + response);
                        }

                        xml.append(encObj);
                    }

                    xml.append(AuthXMLTags.HTTP_SERVLET_RESPONSE_END)
                    .append(AuthXMLTags.REMOTE_REQUEST_RESPONSE_END);
                }
                xml.append(AuthXMLTags.XML_REQUEST_SUFFIX);
                
                // Send the request to be processes
                receivedDocument = processRequest(xml.toString());
                
                // Check set the login status
                checkAndSetLoginStatus();
            } catch (AuthLoginException le) {
                // Login has failed
                loginStatus = Status.FAILED;
                loginException = le;
            }
        }
    }
    
    /**
     * Logs out the user and also invalidates the single sign on token
     * associated with this <code>AuthContext</code>.
     *
     * @throws AuthLoginException if an error occurred during logout.
     *
     * @supported.api
     */
    public void logout() throws AuthLoginException {
        if (localFlag) {
            acLocal.logout();
            return;
        }

        // Construct the XML
        try {
            StringBuilder xml = new StringBuilder(100);
            String[] authHandles = new String[1];
            authHandles[0] = getAuthenticationHandle(receivedDocument);
            xml.append(MessageFormat.format(AuthXMLTags.XML_REQUEST_PREFIX,
            (Object[])authHandles));
            if (appSSOToken != null) {
                xml.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                xml.append(appSSOToken.getTokenID().toString()).
                    append(AuthXMLTags.APPSSOTOKEN_END);
            }
            xml.append(AuthXMLTags.LOGOUT_BEGIN)
               .append(AuthXMLTags.LOGOUT_END)
               .append(AuthXMLTags.XML_REQUEST_SUFFIX);
            
            // Send the request to be processes
            receivedDocument = processRequest(xml.toString());
            
            // Check set the login status
            checkAndSetLoginStatus();
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }

    /**
     * Logs out the user and also invalidates the single sign on token
     * associated with this <code>AuthContext</code>.
	 *
	 * This method causes the logout to happen on the server and the 
	 * correct SPI hooks to be called.
     *
     * @throws AuthLoginException if an error occurred during logout.
     *
     * @supported.api
     */
    public void logoutUsingTokenID()
    throws AuthLoginException {
        if (localFlag) {
            return;
        }

        if (ssoToken != null) {
            try {
                organizationName = ssoToken.getProperty(
                    ISAuthConstants.ORGANIZATION);
                ssoTokenID = ssoToken.getTokenID().toString();
                authURL = sessionCache.getSession(new SessionID(ssoTokenID)).getSessionServiceURL();
            } catch (Exception e) {
                throw new AuthLoginException(e);
            }
        }

        if (authURL != null) {
            authServiceURL = getAuthServiceURL(authURL.getProtocol(),
                authURL.getHost(), Integer.toString(authURL.getPort()),
                authURL.getPath());
        }


        // Construct the XML
        try {
            StringBuilder xml = new StringBuilder(100);
            String[] authHandles = new String[1];
            authHandles[0] = ssoToken.getTokenID().toString();
            xml.append(MessageFormat.format(AuthXMLTags.XML_REQUEST_PREFIX,
            (Object[]) authHandles));
            if (appSSOToken != null) {
                xml.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                xml.append(appSSOToken.getTokenID().toString()).
                    append(AuthXMLTags.APPSSOTOKEN_END);
            }
            xml.append(AuthXMLTags.LOGOUT_BEGIN)
            .append(AuthXMLTags.LOGOUT_END)
            .append(AuthXMLTags.XML_REQUEST_SUFFIX);

            // Send the request to be processes
            receivedDocument = processRequest(xml.toString());

            // Check set the login status
            checkAndSetLoginStatus();
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }
    
    /**
     * Returns login exception, if any, during the authentication process.
     * Typically set when the login fails.
     *
     * @return login exception.
     * @supported.api
     */
    public AuthLoginException getLoginException() {
        if (localFlag) {
            return (acLocal.getLoginException());
        } else {
            return (loginException);
        }
    }
    
    /**
     * Returns the Single-Sign-On (SSO) Token for the authenticated
     * user. If the user has not successfully authenticated
     * <code>Exception</code> will be thrown.
     * <p>
     * Single sign token can be used as the authenticated token.
     *
     * @return Single-Sign-On token for the valid user after successful
     *         authentication.
     * @throws L10NMessageImpl if the user is not authenticated or an error is
     *         encountered in retrieving the user's single sign on token.
     * @supported.api
     */
    public SSOToken getSSOToken() throws L10NMessageImpl {
        if (localFlag) {
            if (!acLocal.getStatus().equals(Status.SUCCESS)) {
                throw new L10NMessageImpl(
                    amAuthContext, "statusNotSuccess", null);
            }
            return (acLocal.getSSOToken());
        } else {
            // Get the loginStatus node
            if (!loginStatus.equals(Status.SUCCESS)) {
                throw new L10NMessageImpl(
                    amAuthContext, "statusNotSuccess", null);
            }
            Node loginStatusNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.LOGIN_STATUS);
            if (loginStatusNode == null) {
                throw new L10NMessageImpl(amAuthContext, "noStatusNode", null);
            }
            
            String ssoTokenIDTmp = XMLUtils.getNodeAttributeValue(loginStatusNode,
                AuthXMLTags.SSOTOKEN);
            try {
                return new com.iplanet.sso.providers.dpro.SSOProviderImpl().
                    createSSOToken(ssoTokenIDTmp, true);
            } catch (SSOException ssoe) {
                throw new L10NMessageImpl(
                    amAuthContext, "createSSOTokenError", null);
            }
        }
    }
    
    /**
     * Returns the current status of the authentication process as
     * <code>AuthContext.Status</code>.
     *
     * @return <code>Status</code> of the authentication process.
     *
     * @supported.api
     */
    public Status getStatus() {
        if (localFlag) {
            return (acLocal.getStatus());
        } else {
            return (loginStatus);
        }
    }
    
    /**
     * Returns the current Auth Identifier of the authentication
     * process as String Session ID.
     *
     * @return Auth Identifier of the authentication process.
     */
    public String getAuthIdentifier() {
        if (localFlag) {
            return (acLocal.getAuthIdentifier());
        } else {
            return (getAuthHandle());
        }
    }
    
    /**
     * Returns the Successful Login URL for the authenticated user.
     *
     * @return the Successful Login URL for the authenticated user.
     * @throws Exception if it fails to get url for auth success
     */
    public String getSuccessURL() throws Exception {
        if (localFlag) {
            if (!acLocal.getStatus().equals(Status.SUCCESS)) {
                throw new
                L10NMessageImpl(amAuthContext, "statusNotSuccess", null);
            }
            return (acLocal.getSuccessURL());
        } else {
            // Get the loginStatus node
            if (!loginStatus.equals(Status.SUCCESS)) {
                throw new
                L10NMessageImpl(amAuthContext, "statusNotSuccess", null);
            }
            Node loginStatusNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.LOGIN_STATUS);
            if (loginStatusNode == null) {
                throw new L10NMessageImpl(amAuthContext, "noStatusNode", null);
            }
            return (XMLUtils.getNodeAttributeValue(loginStatusNode,
            AuthXMLTags.SUCCESS_URL));
        }
    }
    
    /**
     * Returns the Failure Login URL for the authenticating user.
     *
     * @return the Failure Login URL for the authenticating user
     * @throws Exception if it fails to get url for auth failure
     */
    public String getFailureURL() throws Exception {
        if (localFlag) {
            return (acLocal.getFailureURL());
        } else {
            // Get the loginStatus node
            Node loginStatusNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.LOGIN_STATUS);
            if (loginStatusNode == null) {
                throw new L10NMessageImpl(amAuthContext, "noStatusNode", null);
            }
            return (XMLUtils.getNodeAttributeValue(loginStatusNode,
            AuthXMLTags.FAILURE_URL));
        }
    }
    
    /**
     * Resets this instance of <code>AuthContext</code> object, so that a new
     * login process can be initiated. A new authentication process can started
     * using any one of the <code>login</code> methods.
     */
    public void reset() {
        loginStatus = Status.NOT_STARTED;
        //organizationName = null;
        //receivedDocument = null;
        //loginException = null;
    }
    
    /**
     * Returns the the organization name that was set during the
     * <code>AuthContext</code> constructor.
     *
     * @return Organization name in the <code>AuthContext</code>.
     *
     * @supported.api
     */
    public String getOrganizationName() {
        return (this.organizationName);
    }
    
    /**
     *
     * Returns authentication module/s instances (or plugins) configured
     * for a organization, or sub-organization name that was set during the
     * <code>AuthContext</code> constructor.
     *
     * @return Set of Module instance names.
     *
     * @supported.api
     */
    public Set getModuleInstanceNames() {
        if (authURL != null) {
            authServiceURL = getAuthServiceURL(
                authURL.getProtocol(),
                authURL.getHost(), 
                Integer.toString(authURL.getPort()),
                authURL.getPath());
        }
        if (!localFlag) {
            setLocalFlag(authServiceURL);
        }
        if (localFlag) {
            return (acLocal.getModuleInstanceNames());
        } else {
            if (authServiceURL == null) {
                try {
                    authServiceURL = getAuthServiceURL(server_proto,
                        server_host, server_port, server_uri);
                } catch (Exception e) {
                    return Collections.EMPTY_SET;
                }
            }
            sendQueryInformation(AuthXMLTags.MODULE_INSTANCE);
            
            //Receive data
            Node queryResultNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.QUERY_RESULT);
            if (queryResultNode == null) {
                return (null);
            }
            
            // Iteratate through moduleInstanceNames
            HashSet moduleInstanceNames = new HashSet();
            NodeList childNodes = queryResultNode.getChildNodes();
            if ( childNodes != null ) {
                for (int i = 0; i < childNodes.getLength(); i++) {
                    Node childNode = childNodes.item(i);
                    String moduleName = XMLUtils.getValueOfValueNode(childNode);
                    moduleInstanceNames.add(moduleName);
                }
            }
            return (moduleInstanceNames);
        }
    }
    
    /**
     * Terminates an ongoing <code>login</code> call that has not yet completed.
     *
     * @exception AuthLoginException if an error occurred during abort.
     *
     * @supported.api
     */
    public void abort() throws AuthLoginException {
        if (localFlag) {
            acLocal.abort();
            return;
        }
        
        // Construct the XML
        try {
            StringBuilder xml = new StringBuilder(100);
            String[] authHandles = new String[1];
            authHandles[0] = getAuthenticationHandle(receivedDocument);
            xml.append(MessageFormat.format(AuthXMLTags.XML_REQUEST_PREFIX,
            (Object[])authHandles));
            if (appSSOToken != null) {
                xml.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                xml.append(appSSOToken.getTokenID().toString()).
                    append(AuthXMLTags.APPSSOTOKEN_END);
            }
            xml.append(AuthXMLTags.ABORT_BEGIN)
            .append(AuthXMLTags.ABORT_END)
            .append(AuthXMLTags.XML_REQUEST_SUFFIX);
            
            // Send the request to be processes
            receivedDocument = processRequest(xml.toString());
            
            // Check set the login status
            checkAndSetLoginStatus();
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }
    
    /**
     * Sets the password for the certificate database.
     * It is required to call only once to initialize certificate database if
     * the password is not set in the password file (specified as
     * the value for <code>com.iplanet.am.admin.cli.certdb.passfile</code>
     * in <code>AMConfig.properties</code>). If both are set, this method will
     * overwrite the value in certificate password file.
     *
     * @param password Password for the certificate database.
     *
     * @supported.api
     */
    public static void setCertDBPassword(String password) {
        try {
            if (usingJSSEHandler) {
                Class pcbClass = (Class) Class.forName(JSSE_PASSWORD_CALLBACK);
                Object passwdCallback = (Object) pcbClass.newInstance();
                Method method =
                pcbClass.getMethod("setPassword", new Class[] { String.class });
                KeyStore keystore = (KeyStore)method.invoke(
                    passwdCallback, new Object[] { password });
            } else {
                Class initializer = Class.forName(JSS_PASSWORD_UTIL);
                Constructor initializerConstructor = initializer.getConstructor(
                    new Class[] { String.class });
                initializerConstructor.newInstance(new Object[] { password });
            }
        } catch (Exception e) {
            e.printStackTrace();
            authDebug.message("Error in setCertDBPassword : " + e.getMessage());
        }
    }
    
    
    /**
     * Returns the error template.
     *
     * @return error template.
     */
    public String getErrorTemplate() {
        if (localFlag) {
            return (acLocal.getErrorTemplate());
        } else {
            if (receivedDocument == null) {
                //something went terribly wrong, let's return with internal error template
                return AuthClientUtils.getErrorTemplate(AMAuthErrorCode.AUTH_ERROR);
            }
            String errTemplate = "";
            Node exceptionNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.EXCEPTION);
            if (exceptionNode != null) {
                errTemplate = XMLUtils.getNodeAttributeValue(exceptionNode,
                AuthXMLTags.TEMPLATE_NAME);
            }
            return errTemplate;
        }
    }
    
    /**
     * Returns the error message.
     *
     * @return error message.
     */
    public String getErrorMessage() {
        if (localFlag) {
            return (acLocal.getErrorMessage());
        } else {
            if (receivedDocument == null) {
                //something went terribly wrong, let's return with internal error message
                return AuthClientUtils.getErrorMessage(AMAuthErrorCode.AUTH_ERROR);
            }
            String errMessage = null;
            Node exceptionNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.EXCEPTION);
            if (exceptionNode != null) {
                errMessage = XMLUtils.getNodeAttributeValue(exceptionNode,
                AuthXMLTags.MESSAGE);
            }
            return errMessage;
        }
    }
    
    /**
     * Returns error code.
     *
     * @return error code with white space trimmed
     */
    public String getErrorCode() {
        if (localFlag) {
            return (acLocal.getErrorCode());
        } else {
            if (receivedDocument == null) {
                //something went terribly wrong
                return AMAuthErrorCode.AUTH_ERROR;
            }
            String errCode = "";
            Node exceptionNode = XMLUtils.getRootNode(receivedDocument,
            AuthXMLTags.EXCEPTION);

            if (exceptionNode != null) {
                errCode = XMLUtils.getNodeAttributeValue(exceptionNode,
                AuthXMLTags.ERROR_CODE);
            }

            if (errCode != null) {
                return errCode.trim();
            } else {
                return errCode;
            }
        }
    }
    
    /**
     * Sets the client's hostname or IP address.This could be used
     * by the policy component to restrict access to resources.
     * This method is ineffective if the "Remote Auth Security" option under 
     * the global configuration of Core Authentication Service is not enabled.
     * This method must be called before calling <code>login</code> method.
     * If it is called after calling <code>login</code> then 
     * it is ineffective.
     *
     * @param hostname hostname or ip address
     *
     * @supported.api
     */
    public void setClientHostName(String hostname) {
        this.hostName = hostname;
    }

    /**
     * Returns the client's hostname or IP address as set by 
     * setClientHostName
     * 
     * @return hostname/IP address
     *
     * @supported.api
     */
    public String getClientHostName() {
        return (hostName);
    }

    /**
     * Sets locale based on user locale preferemce.
     *
     * @param loc locale preference of user
     */
    public void setLocale (java.util.Locale loc) {
        clientLocale = loc;
    }

    /**
     * Returns locale preference set in AuthConext
     * @return - user prefered locale.
     */

    public java.util.Locale getLocale () {
        return clientLocale;
    }
    
    private AuthLoginException checkException(){
        AuthLoginException exception = null;
        String error = getErrorCode();

        // if the app token is invalid, refresh the token
        if (error != null && error.equals(AMAuthErrorCode.REMOTE_AUTH_INVALID_SSO_TOKEN)) {
            appSSOToken = getAppSSOToken(true);
        }

        if (error != null && error.length() != 0){
            exception = new AuthLoginException("amAuth", error, null);
        } else {
            error = getErrorMessage();
            if (error != null && error.length() != 0) {
                exception = new AuthLoginException(error);
            }
        }
        return exception;
    }
    
    protected void checkAndSetLoginStatus(){
        
        Node loginStatusNode = XMLUtils.getRootNode(
        receivedDocument, AuthXMLTags.LOGIN_STATUS);
        if (loginStatusNode == null) {
            loginException = checkException();
            
            if (includeReqRes) {
                remoteRequest = AuthXMLUtils.getRemoteRequest(
                    XMLUtils.getRootNode(receivedDocument, AuthXMLTags.REMOTE_REQUEST_RESPONSE));
                remoteResponse = AuthXMLUtils.getRemoteResponse(
                    XMLUtils.getRootNode(receivedDocument, AuthXMLTags.REMOTE_REQUEST_RESPONSE));
            }
        } else {
            //since there was no error, we should reset the loginException, to handle the case when the first auth
            //server was not available.
            loginException = null;
            // Get the status attribute
            String status = XMLUtils.getNodeAttributeValue(
            loginStatusNode, AuthXMLTags.STATUS);
            if (status != null) {
                if (status.equals(Status.SUCCESS.toString())) {
                    loginStatus = Status.SUCCESS;
                } else if (status.equals(Status.FAILED.toString())) {
                    loginStatus = Status.FAILED;
                    loginException = checkException();
                } else if (status.equals(Status.COMPLETED.toString())) {
                    loginStatus = Status.COMPLETED;
                } else if (status.equals(Status.IN_PROGRESS.toString())) {
                    loginStatus = Status.IN_PROGRESS;
                } else if (status.equals(Status.RESET.toString())) {
                    loginStatus = Status.RESET;
                }
            }

            if (includeReqRes) {
                remoteRequest = AuthXMLUtils.getRemoteRequest(
                    XMLUtils.getRootNode(receivedDocument, AuthXMLTags.REMOTE_REQUEST_RESPONSE));
                remoteResponse = AuthXMLUtils.getRemoteResponse(
                    XMLUtils.getRootNode(receivedDocument, AuthXMLTags.REMOTE_REQUEST_RESPONSE));
            }

            if (authDebug.messageEnabled()) {
                authDebug.message("LoginStatus : " + loginStatus);
            }
        }
    }
    
    protected void sendQueryInformation(String reqInfo) {
        // Construct the XML
        try {
            StringBuilder xml = new StringBuilder(100);
            String[] authHandles = new String[1];
            authHandles[0] = getAuthHandle();
            
            xml.append(MessageFormat.format(AuthXMLTags.XML_REQUEST_PREFIX,
            (Object[])authHandles));
            if (appSSOToken != null) {
                xml.append(AuthXMLTags.APPSSOTOKEN_BEGIN);
                xml.append(appSSOToken.getTokenID().toString()).
                    append(AuthXMLTags.APPSSOTOKEN_END);
            }
            xml.append(AuthXMLTags.QUERY_INFO_BEGIN)
               .append(AuthXMLTags.SPACE)
               .append(AuthXMLTags.REQUESTED_INFO)
               .append(AuthXMLTags.EQUAL)
               .append(AuthXMLTags.QUOTE)
               .append(reqInfo)
               .append(AuthXMLTags.QUOTE);

            if (authHandles[0].equals("0")) {
                xml.append(AuthXMLTags.SPACE)
                    .append(AuthXMLTags.ORG_NAME_ATTR)
                    .append(AuthXMLTags.EQUAL)
                    .append(AuthXMLTags.QUOTE)
                    .append(XMLUtils.escapeSpecialCharacters(organizationName))
                    .append(AuthXMLTags.QUOTE);
            }

            xml.append(AuthXMLTags.ELEMENT_END)
                .append(AuthXMLTags.QUERY_INFO_END)
                .append(AuthXMLTags.XML_REQUEST_SUFFIX);
            
            // Send the request to be processes
            receivedDocument = processRequest(xml.toString());
            
            // Check set the login status
            checkAndSetLoginStatus();
        } catch (AuthLoginException le) {
            // Login has failed
            loginStatus = Status.FAILED;
            loginException = le;
        }
    }
    
    private void setLocalFlag(URL url) {
        try {
            String urlStr = url.getProtocol() + "://" + url.getHost() + ":"
                + Integer.toString(url.getPort());
            
            if (authDebug.messageEnabled()) {
                authDebug.message("in setLocalFlag(), url : " + urlStr);
                authDebug.message("AuthContext.localAuthServiceID : " +
                    localAuthServiceID);
            }
            
            if ((localAuthServiceID != null) &&
                (urlStr.equalsIgnoreCase(localAuthServiceID))
            ) {
                localFlag = true;
            }
        } catch (Exception e) {
            authDebug.error("AuthContext::setLocalFlag:: " + e);
        }
    }
    
    protected Document processRequest(String xmlRequest)
            throws AuthLoginException {
        Document doc = null;

        try {
            Request request = new Request(xmlRequest);
            RequestSet set = new RequestSet(AuthXMLTags.AUTH_SERVICE);
            set.addRequest(request);
            
            URL url = authServiceURL;
            
            if (url.getProtocol().equals("https") && (nickName != null)) {
                Class[] paramtype = {String.class};
                Object[] param = {nickName};
                String protHandler = protHandlerPkg + ".https.Handler";
                Constructor construct =
                    Class.forName(protHandler).getConstructor(paramtype);
                URLStreamHandler handler =
                    (URLStreamHandler)construct.newInstance(param);
                url = new URL(url.getProtocol(), url.getHost(), url.getPort(),
                url.getFile(), handler);
            }
            
            if (authDebug.messageEnabled()) {
                authDebug.message("Service URL : " + url.toString());
            }

            Vector responses = PLLClient.send(url, set, cookieTable);
            
            if ((responses.isEmpty()) || (responses.size() != 1)) {
                throw new L10NMessageImpl(amAuthContext, "responseError", null);
            }
            
            Response res = (Response) responses.elementAt(0);
            String responseStr = (String)res.getContent();
            
            doc = XMLUtils.getXMLDocument(
                new ByteArrayInputStream(responseStr.getBytes("UTF-8")));
        } catch (Exception e) {
            authDebug.message("error in getting service url", e);
            throw new AuthLoginException(amAuthContext, "xmlProcessError",
                null, e);
        }
        return (doc);
    }
    
    protected static void checkForException(Document document)
            throws AuthLoginException {
        Node exceptionNode = XMLUtils.getRootNode(
            document, AuthXMLTags.EXCEPTION);

        if (exceptionNode != null) {
            throw (new AuthLoginException(XMLUtils.getNodeAttributeValue(
                exceptionNode, AuthXMLTags.MESSAGE)));
        }
    }
    
    protected String getAuthenticationHandle(Document document)
            throws AuthLoginException {
        Node responseNode = XMLUtils.getRootNode(
            document, AuthXMLTags.RESPONSE);
        if (responseNode == null) {
            throw new AuthLoginException(amAuthContext, "responseError", null);
        }
        
        String authID = XMLUtils.getNodeAttributeValue(
            responseNode, AuthXMLTags.AUTH_ID_HANDLE);
        return (authID);
    }
    
    protected static Callback[] getCallbacks(
        Document document,
        boolean noFilter) {
        return (AuthXMLUtils.getCallbacks(XMLUtils.getRootNode(document,
            AuthXMLTags.CALLBACKS), noFilter));
    }
    
    protected static Subject getSubject(Document document) {
        Node loginStatusNode = XMLUtils.getRootNode(document,
            AuthXMLTags.LOGIN_STATUS);

        if (loginStatusNode == null) {
            return (null);
        }
        
        Node subjectNode = XMLUtils.getChildNode(loginStatusNode,
        AuthXMLTags.SUBJECT);
        
        if (subjectNode == null) {
            return (null);
        }
        
        String subject = XMLUtils.getValueOfValueNode(subjectNode);
        try {
            Subject sSubject = AuthXMLUtils.getDeSerializedSubject(subject);
            
            if (authDebug.messageEnabled()) {
                authDebug.message("Deserialized subject : "
                    + sSubject.toString());
            }
            return sSubject;
        } catch (Exception e) {
            authDebug.message("get Deserialized subject error : " , e);
            return null;
        }
        
    }
    
    protected static String getXMLforSubject(Subject subject) {
        if (subject == null) {
            return ("");
        }
        StringBuilder request = new StringBuilder(100);
        request.append(AuthXMLTags.SUBJECT_BEGIN);
        String serializeSubject = AuthXMLUtils.getSerializedSubject(subject);
        request.append(serializeSubject);
        request.append(AuthXMLTags.SUBJECT_END);
        return (request.toString());
    }
    
    /**
     * Returns the account lockout message. This can be either a dynamic
     * message indicating the number of tries left or the the account
     * deactivated message.
     *
     * @return account lockout message.
     */
    public String getLockoutMsg() {
        String lockoutMsg = null;
        if (localFlag) {
            lockoutMsg = acLocal.getLockoutMsg();
        } else {
            // Account Lockout Warning Check by scanning the error
            // message in the exception thrown by the server
            lockoutMsg = getErrorMessage();
            if((lockoutMsg == null) ||
                (lockoutMsg.indexOf("Account lockout") == -1)){
                lockoutMsg = "";
            }
        }
        return lockoutMsg;
    }
    
    /**
     * Returns <code>true</code> if account is lock out.
     *
     * @return <code>true</code> if account is lock out.
     */
    public boolean isLockedOut() {
        boolean isLockedOut = false;
        if (localFlag) {
            isLockedOut = acLocal.isLockedOut();
        } else {
            // TBD
        }
        
        return isLockedOut;
    }
    
    /**
     * The class <code>Status</code> defines the possible
     * authentication states during the login process.
     *
     * @supported.all.api
     */
    public static class Status extends Object {
        
        private String status;
        
        /**
         * The <code>NOT_STARTED</code> status indicates that the login process
         * has not yet started. Basically, it means that the method
         * <code>login</code> has not been called.
         */
        public static final Status NOT_STARTED = new Status("not_started");
        
        /**
         * The <code>IN_PROGRESS</code> status indicates that the login process
         * is in progress. Basically, it means that the <code>login</code>
         * method has been called and that this object is waiting for the user
         * to send authentication information.
         */
        public static final Status IN_PROGRESS = new Status("in_progress");
        
        /**
         *
         * The <code>SUCCESS</code> indicates that the login process has
         * succeeded.
         */
        public static final Status SUCCESS = new Status("success");
        
        /**
         * The <code>FAILED</code> indicates that the login process has failed.
         */
        public static final Status FAILED = new Status("failed");
        
        /**
         *
         * The <code>COMPLETED</code> indicates that the user has been
         * successfully logged out.
         */
        public static final Status COMPLETED = new Status("completed");
        
        /**
         * The <code>RESET</code> indicates that the login process has been
         * reset or re-initialized.
         */
        public static final Status RESET = new Status("reset");
        
        /**
         * The <code>ORG_MISMATCH</code> indicates that the framework
         * <code>org</code> and the <code>org</code> required by the user do
         * not match.
         */
        public static final Status ORG_MISMATCH = new Status("org_mismatch");
        
        
        private Status() {
            // do nothing
        }
        
        private Status(String s) {
            status = s;
        }
        
        /**
         * Returns the string representation of the authentication status.
         *
         * @return String representation of authentication status.
         */
        public String toString() {
            return (status);
        }
        
        /**
         * Checks if two authentication status objects are equal.
         *
         * @param authStatus Reference object with which to compare.
         * @return <code>true</code> if the objects are same.
         */
        public boolean equals(Object authStatus) {
            if (authStatus instanceof Status) {
                Status s = (Status) authStatus;
                return (s.status.equalsIgnoreCase(status));
            }
            return (false);
        }
    }
    
    /**
     * The class <code>IndexType</code> defines the possible kinds of "objects"
     * or "resources" for which an authentication can be performed.
     *
     * @supported.all.api
     */
    public static class IndexType extends Object {
        
        private String index;
        
        /**
         * The <code>USER</code> index type indicates that the index name given
         * corresponds to a user.
         */
        public static final IndexType USER = new IndexType("user");
        
        /**
         * The <code>ROLE</code> index type indicates that the index name given
         * corresponds to a role.
         */
        public static final IndexType ROLE = new IndexType("role");
        
        /**
         *
         * The <code>SERVICE</code> index type indicates that the index name
         * given corresponds to a service (or application).
         */
        public static final IndexType SERVICE = new IndexType("service");
        
        /**
         * The <code>LEVEL</code> index type indicates that the index name
         * given corresponds to a given authentication level.
         */
        public static final IndexType LEVEL = new IndexType("level");
        
        /**
         * The <code>MODULE_INSTANCE</code> index type indicates that the index
         * name given corresponds to one of the authentication modules.
         */
        public static final IndexType MODULE_INSTANCE =
            new IndexType("module_instance");
        
        /**
         * The <code>RESOURCE</code> index type indicates that the index
         * name given corresponds to a given policy protected resource URL.
         */
        public static final IndexType RESOURCE =
            new IndexType("resource");
        
        /**
         * The <code>COMPOSITE_ADVICE</code> index type indicates that the
         * index name given corresponds to string in the form of XML
         * representing different Policy Authentication conditions, example
         * <code>AuthSchemeCondition</code>, <code>AuthLevelCondition</code>,
         * etc.
         */
        public static final IndexType COMPOSITE_ADVICE =
            new IndexType("composite_advice");
        
        private IndexType() {
            // do nothing
        }
        
        private IndexType(String s) {
            index = s;
        }
        
        /**
         * Returns the string representation of the index type.
         *
         * @return String representation of index type.
         */
        public String toString() {
            return (index);
        }
        
        /**
         * Checks if two index type objects are equal.
         *
         * @param indexType Reference object with which to compare.
         *
         * @return <code>true</code> if the objects are same.
         */
        public boolean equals(Object indexType) {
            if (indexType instanceof IndexType) {
                IndexType s = (IndexType) indexType;
                return (s.index.equalsIgnoreCase(index));
            }
            return (false);
        }
    }
    
    private String getAuthHandle() {
        String handle = null;
        
        if (receivedDocument != null) {
            try {
                handle = getAuthenticationHandle(receivedDocument);
            } catch (Exception e) {
                // do nothing
            }
        }
        if ( handle == null ) {
            handle = "0";
        }
        return handle;
    }
    
    private static URL getAuthServiceURL(
        String protocol,
        String host,
        String port,
        String uri
    ) {
        URL authservice = null;
        try {
            authservice = WebtopNaming.getServiceURL(AuthXMLTags.AUTH_SERVICE,
                protocol, host, port, uri);
        } catch (Exception e) {
            authDebug.error("Failed to obtain auth service url from server: " +
            protocol + "://" + host + ":" + port);
        }
        return authservice;
    }
    
    private void onSuccessLocal() {
        if (localSessionChecked) {
            return;
        }
        SSOToken currToken = acLocal.getSSOToken();
        com.iplanet.dpro.session.service.InternalSession oldSess
            = acLocal.getLoginState().getOldSession();
        if (oldSess != null) {
            if (forceAuth) {
                try {
                    SSOTokenManager.getInstance().
                        destroyToken(currToken);
                } catch (SSOException ssoExp) {
                    authDebug.error("AuthContext.onSuccessLocal: ",
                        ssoExp);
    	
                }
                acLocal.getLoginState().setSession(oldSess);
                acLocal.getLoginState().setForceAuth(false);
                ssoToken = acLocal.getSSOToken();
                ssoTokenID = ssoToken.getTokenID().toString();
                
            } else {
                InjectorHolder.getInstance(SessionService.class).destroyInternalSession(oldSess.getID());
            }
        }
        localSessionChecked = true;
    }

    /**
     * Returns the application sso token. Can perform a check to ensure that
     * the app token is still valid (requires a session refresh call to OpenAM)
     *
     * @param refresh true if we should check with OpenAM if the app token is valid
     * @return a valid application's sso token.
     */
    private SSOToken getAppSSOToken(boolean refresh) {
        SSOToken appToken = null;

        try {
            appToken = (SSOToken) AccessController.doPrivileged(
                            AdminTokenAction.getInstance());
        } catch (AMSecurityPropertiesException aspe) {
            if (authDebug.messageEnabled()) {
                authDebug.message("AuthContext::getAppSSOToken: " +
                                  "unable to get app ssotoken " + aspe.getMessage());
            }
        }

        if (refresh) {
            // ensure the token is valid
            try {
                SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance();
                ssoTokenManager.refreshSession(appToken);

                if (!ssoTokenManager.isValidToken(appToken)) {
                    if (authDebug.messageEnabled()) {
                        authDebug.message("AuthContext.getAppSSOToken(): " +
                                          "App SSOToken is invalid, retrying");
                    }

                    try {
                        appToken = (SSOToken) AccessController.doPrivileged(
                                                AdminTokenAction.getInstance());
                    } catch (AMSecurityPropertiesException aspe) {
                        if (authDebug.messageEnabled()) {
                            authDebug.message("AuthContext::getAppSSOToken: " +
                                              "unable to get app ssotoken " + aspe.getMessage());
                        }
                    }
                }
            } catch (SSOException ssoe) {
                if (authDebug.messageEnabled()) {
                    authDebug.message("AuthContext.getAppSSOToken(): " +
                                      "unable to refresh app token: " + ssoe.getL10NMessage());
                }

                try {
                    appToken = (SSOToken) AccessController.doPrivileged(
                                            AdminTokenAction.getInstance());
                } catch (AMSecurityPropertiesException aspe) {
                    if (authDebug.errorEnabled()) {
                        authDebug.error("AuthContext::getAppSSOToken: " +
                                          "unable to get app ssotoken " + aspe.getMessage());
                    }
                }
            }
        }

        if (authDebug.messageEnabled()) {
            if (appToken == null) {
                authDebug.message("Null App SSO Token");
            } else {
                authDebug.message("Obtained App Token= " + appToken.getTokenID().toString());
            }
        }

        return appToken;
    }

    public AuthContextLocal getAuthContextLocal() {
        return acLocal;
    }
}
