/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2006 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: FSAssertionManager.java,v 1.12 2009/08/03 18:18:36 bigfatrat Exp $
 *
 * Portions Copyrighted 2016 ForgeRock AS.
 */

package com.sun.identity.federation.services;

import static org.forgerock.openam.utils.Time.*;

import com.sun.identity.common.PeriodicGroupRunnable;
import com.sun.identity.common.ScheduleableGroupAction;
import com.sun.identity.common.SystemConfigurationUtil;
import com.sun.identity.common.SystemTimerPool;
import com.sun.identity.common.TimerPool;
import com.sun.identity.federation.common.FSException;
import com.sun.identity.federation.common.FSUtils;
import com.sun.identity.federation.common.IFSConstants;
import com.sun.identity.federation.common.LogUtil;
import com.sun.identity.federation.jaxb.entityconfig.BaseConfigType;
import com.sun.identity.federation.message.FSAssertion;
import com.sun.identity.federation.message.FSAssertionArtifact;
import com.sun.identity.federation.message.FSAuthenticationStatement;
import com.sun.identity.federation.message.FSSubject;
import com.sun.identity.federation.message.common.AuthnContext;
import com.sun.identity.federation.message.common.IDPProvidedNameIdentifier;
import com.sun.identity.federation.meta.IDFFMetaException;
import com.sun.identity.federation.meta.IDFFMetaManager;
import com.sun.identity.federation.meta.IDFFMetaUtils;
import com.sun.identity.federation.services.util.FSAttributeStatementHelper;
import com.sun.identity.federation.services.util.FSServiceUtils;
import com.sun.identity.plugin.monitoring.FedMonAgent;
import com.sun.identity.plugin.monitoring.FedMonIDFFSvc;
import com.sun.identity.plugin.monitoring.MonitorManager;
import com.sun.identity.plugin.session.SessionException;
import com.sun.identity.plugin.session.SessionManager;
import com.sun.identity.plugin.session.SessionProvider;
import com.sun.identity.saml.assertion.Advice;
import com.sun.identity.saml.assertion.Assertion;
import com.sun.identity.saml.assertion.AssertionIDReference;
import com.sun.identity.saml.assertion.AttributeStatement;
import com.sun.identity.saml.assertion.AudienceRestrictionCondition;
import com.sun.identity.saml.assertion.Conditions;
import com.sun.identity.saml.assertion.NameIdentifier;
import com.sun.identity.saml.assertion.SubjectConfirmation;
import com.sun.identity.saml.assertion.SubjectLocality;
import com.sun.identity.saml.common.SAMLConstants;
import com.sun.identity.saml.common.SAMLException;
import com.sun.identity.saml.common.SAMLServiceManager;
import com.sun.identity.saml.common.SAMLUtils;
import com.sun.identity.saml.protocol.AssertionArtifact;
import com.sun.identity.saml.protocol.Status;
import com.sun.identity.shared.stats.Stats;
import com.sun.identity.shared.DateUtils;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

/**
 * IDP side class which handles assertion and artifact operations.
 */
public final class FSAssertionManager {
    /**
     * Statistics instance for artifacts.
     */
    public static Stats artStats = Stats.getInstance("libIDFFArtifactMap");
    /**
     * Statistics instance for assertions.
     */
    public static Stats assrtStats = Stats.getInstance("libIDFFAssertionMap");
    private static final Integer DEFAULT_CLEANUP_INTERVAL =
        new Integer(IFSConstants.CLEANUP_INTERVAL_DEFAULT);
    private static final Integer DEFAULT_ASSERTION_TIMEOUT =
        new Integer(IFSConstants.ASSERTION_TIMEOUT_DEFAULT);

    private static Map instanceMap = new HashMap();
    
    private Map artIdMap = null;
    private Map idEntryMap = null;
    private PeriodicGroupRunnable assertionTimeoutRunnable;
    private PeriodicGroupRunnable artifactTimeoutRunnable;
    private static String SERVICE_NAMING = "fsassertionmanager";
    
    private int cleanupInterval;
    private int assertionTimeout;
    private int artifactTimeout;
    private FSArtifactStats artIdStats;
    private FSAssertionStats assrtIdStats;

    private static FedMonAgent agent = MonitorManager.getAgent();
    private static FedMonIDFFSvc idffSvc = MonitorManager.getIDFFSvc();

    private class Entry {
        private String destID = null;
        private String artString = null;
        private Object token = null;
        private Assertion assertion = null;
        
        public Entry(
            Assertion assertion,
            String destID,
            String artString,
            Object token)
        {
            this.assertion = assertion;
            this.destID = destID;
            this.artString = artString;
            this.token = token;
        }
        
        public Assertion getAssertion() {
            return assertion;
        }
        
        public String getDestID() {
            return destID;
        }
        
        public String getArtifactString() {
            return artString;
        }
        
        public Object getSessionToken() {
            return token;
        }
    }
    
    private String hostEntityId = null;
    private String realm = null;
    private String metaAlias = null;
    
    private FSAssertionManager(String metaAlias) 
        throws FSException
    {
        idEntryMap = new HashMap();
        artIdMap = new HashMap();

        this.metaAlias = metaAlias;
        realm = IDFFMetaUtils.getRealmByMetaAlias(metaAlias);
        try {
            IDFFMetaManager metaManager = FSUtils.getIDFFMetaManager();
            if (metaManager != null) {
                hostEntityId = metaManager.getEntityIDByMetaAlias(metaAlias);
            }
        } catch (IDFFMetaException ie) {
            throw new FSException(ie);
        }
        if (hostEntityId == null) {
            throw new FSException("nullProviderID", null);
        }
        Map attributes;
        cleanupInterval = IFSConstants.CLEANUP_INTERVAL_DEFAULT * 1000;
        assertionTimeout = IFSConstants.ASSERTION_TIMEOUT_DEFAULT * 1000;
        artifactTimeout = IFSConstants.ARTIFACT_TIMEOUT_DEFAULT * 1000;
        try {
            BaseConfigType idpConfig = FSUtils.getIDFFMetaManager().
                getIDPDescriptorConfig(realm, hostEntityId);
            attributes = IDFFMetaUtils.getAttributes(idpConfig);
            try {
                cleanupInterval = Integer.parseInt(
                    IDFFMetaUtils.getFirstAttributeValue(attributes,
                    IFSConstants.CLEANUP_INTERVAL)) * 1000;
            } catch (Exception e) {
                FSUtils.debug.error("FSAssertionManager: "
                    + "Exception while parsing interval", e);
            }
            try {
                assertionTimeout = Integer.parseInt(
                    IDFFMetaUtils.getFirstAttributeValue(attributes, 
                    IFSConstants.ASSERTION_INTERVAL)) * 1000;
            } catch (Exception e) {
                FSUtils.debug.error("AssertionManager: "
                    + "Exception while parsing timeout", e);
            }
            if (assertionTimeout <
                IFSConstants.ASSERTION_TIMEOUT_ALLOWED_DIFFERENCE) {
                assertionTimeout =
                    IFSConstants.ASSERTION_TIMEOUT_ALLOWED_DIFFERENCE;
            }
            try {
                artifactTimeout = Integer.parseInt(
                    IDFFMetaUtils.getFirstAttributeValue(attributes,
                    IFSConstants.ARTIFACT_TIMEOUT)) * 1000;
            } catch(Exception ex){
                if (FSUtils.debug.messageEnabled()) {
                    FSUtils.debug.message(
                        "FSAssertionManager: "
                        + "ArtifactTimeOut configuration not found in FSConfig."
                        + " Using Default");
                }
            }
        } catch(Exception e){
            FSUtils.debug.error("FSAssertionManager: "
                + "Exception while parsing cleanup assertion :", e);
        }
        ScheduleableGroupAction timeoutAction = new
            ScheduleableGroupAction() {
            public void doGroupAction(Object obj) {
                Entry entry = (Entry) idEntryMap.remove(obj);
                if ((agent != null) && agent.isRunning() && (idffSvc != null)){
                    idffSvc.setAssertions((long)idEntryMap.size());
                }
                if (entry != null) {
                    String artString = entry.getArtifactString();
                    if (artString != null) {
                        artIdMap.remove(artString);
                    }
                    if ((agent != null) &&
                        agent.isRunning() &&
                        (idffSvc != null))
                    {
                        idffSvc.setArtifacts((long)artIdMap.size());
                    }
                }
            }
        };
        assertionTimeoutRunnable = new PeriodicGroupRunnable(timeoutAction,
            cleanupInterval, assertionTimeout, true);
        artifactTimeoutRunnable = new PeriodicGroupRunnable(timeoutAction,
            cleanupInterval, artifactTimeout, true);
        TimerPool pool = SystemTimerPool.getTimerPool();
        pool.schedule(assertionTimeoutRunnable, new Date(((
                currentTimeMillis() + cleanupInterval) / 1000) * 1000));
        pool.schedule(artifactTimeoutRunnable, new Date(((
                currentTimeMillis() + cleanupInterval) / 1000) * 1000));
        
        if (assrtStats.isEnabled()) {
            assrtIdStats = new FSAssertionStats(idEntryMap, realm,hostEntityId);
            assrtStats.addStatsListener(assrtIdStats);

            artIdStats = new FSArtifactStats(artIdMap, realm, hostEntityId);
            artStats.addStatsListener(artIdStats);
        }

    }
    
    /**
     * Returns hosted provider meta alias.
     * @return hosted provider meta alias.
     * @see #setMetaAlias(String)
     */
    public String getMetaAlias() {
        return metaAlias; 
    }

    /**
     * Sets hosted provider meta alias.
     * @param metaAlias hosted provider meta alias
     * @see #getMetaAlias()
     */
    public void setMetaAlias(String metaAlias) {
        this.metaAlias = metaAlias;
    }

    /**
     * Returns the realm which hosted provider resides.
     * @return the realm which hosted provider resides.
     * @see #setRealm(String)
     */
    public String getRealm() {
        return realm; 
    }

    /**
     * Sets the realm which hosted provider resides.
     * @param realm The realm which hosted provider resides
     * @see #getRealm()
     */
    public void setRealm(String realm) {
        this.realm = realm;
    }

    /**
     * Returns hosted provider Entity ID.
     * @return hosted provider Entity ID
     * @see #setEntityId(String)
     */
    public String getEntityId() {
        return hostEntityId; 
    }

    /**
     * Sets hosted provider Entity ID.
     * @param entityId hosted provider Entity ID
     * @see #getEntityId()
     */
    public void setEntityId(String entityId) {
        this.hostEntityId = entityId;
    }

    /**
     * Returns artifact to assertion ID map.
     * @return artifact to assertion ID map
     */
    public Map getArtIdMap() {
        return artIdMap;
    }

    /**
     * Returns assertion ID to <code>Entry</code> object map.
     * @return assertion ID to <code>Entry</code> object map
     */
    public Map getIdEntryMap() {
        return idEntryMap;
    }

    /**
     * Returns a <code>FSAssertionManager</code> instance.
     * @param metaAlias hosted entity ID
     * @return <code>FSAssertionManager</code> instance.
     * @exception FSException if error occurrs.
     */
    public static synchronized FSAssertionManager getInstance(
        String metaAlias)
        throws FSException
    { 

        FSUtils.debug.message("FSAssertionManager.getInstance: Called");
        FSAssertionManager instance = 
            (FSAssertionManager) instanceMap.get(metaAlias);
        if (instance == null) {
            if (FSUtils.debug.messageEnabled() ) {
                FSUtils.debug.message("FSAssertionManager.getInstance: " +
                    "Constructing a new instance of FSAssertionManager");
            }
            instance = new FSAssertionManager(metaAlias);
            synchronized (instanceMap) {
                instanceMap.put(metaAlias, instance);
            }
        }
        return(instance);
    }
    

    /**
     * Creates an assertion artifact.
     * @param id session ID
     * @param realm the realm in which the provider resides
     * @param spEntityID service provider's entity ID
     * @param spHandle service provider issued <code>NameIdentifier</code>
     * @param idpHandle identity provider issued <code>NameIdentifier</code>
     * @param inResponseTo value to InResponseTo attribute. It's the request ID.
     * @param minorVersion request minor version, used to determine assertion's
     *  minor version
     * @exception FSException,SAMLException if error occurrs
     */
    public AssertionArtifact createFSAssertionArtifact(
        String id,
        String realm,
        String spEntityID,
        NameIdentifier spHandle,
        NameIdentifier idpHandle,
        String inResponseTo,
        int minorVersion)
    throws FSException, SAMLException  
    {
        // check input
        if ((id == null) ||(spEntityID == null)) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager: null input for"
                    + " method createFSAssertionArtifact.");
            }
            throw new FSException("nullInput",null);
        }
        
        // create assertion id and artifact
        String handle = SAMLUtils.generateAssertionHandle();
        if (handle == null) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.createFSAssertionArt"
                 + "ifact: couldn't generate assertion handle.");
            }
            throw new FSException("errorCreateArtifact", null);
        }
        
        // TODO: should obtain it through meta
        String sourceSuccinctID = FSUtils.generateSourceID(hostEntityId);
        byte bytesSourceId[] = SAMLUtils.stringToByteArray(sourceSuccinctID);
        byte bytesHandle[] = null;
        try{
            bytesHandle =  handle.getBytes(IFSConstants.SOURCEID_ENCODING);
        } catch(Exception e){
            FSUtils.debug.error(
                "FSAssertionManager.createFSAssertionArt: ", e);
            return null;
        }
        AssertionArtifact art = new FSAssertionArtifact(
            bytesSourceId, bytesHandle);
        int assertionMinorVersion = IFSConstants.FF_11_ASSERTION_MINOR_VERSION;
        if (minorVersion == IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
            assertionMinorVersion = 
                IFSConstants.FF_12_ART_ASSERTION_MINOR_VERSION;
        }
        Assertion assertion =
            createFSAssertion(id,
                              art,
                              realm,
                              spEntityID,
                              spHandle,
                              idpHandle,
                              inResponseTo,
                              assertionMinorVersion);
        return art;
    }
    
    /**
     * Creates an assertion artifact.
     * @param id session ID
     * @param artifact assertion artifact
     * @param realm the realm under which the entity resides.
     * @param spEntityID service provider's entity ID
     * @param spHandle service provider issued <code>NameIdentifier</code>
     * @param idpHandle identity provider issued <code>NameIdentifier</code>
     * @param inResponseTo value to InResponseTo attribute. It's the request ID.
     * @param assertionMinorVersion minor version the assertion should use
     * @exception FSException,SAMLException if error occurrs
     */
    public FSAssertion createFSAssertion(
        String id,
        AssertionArtifact artifact,
        String realm,
        String spEntityID,
        NameIdentifier spHandle,
        NameIdentifier idpHandle,
        String inResponseTo,
        int assertionMinorVersion)
    throws FSException, SAMLException
    {
        FSUtils.debug.message(
            "FSAssertionManager.createFSAssertion(id): Called");
        // check input
        if ((id == null) ||(spEntityID == null)) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager: null input for"
                    + " method createFSAssertion.");
            }
            throw new FSException("nullInput",null);
        }
        
        String destID = spEntityID;
        
        String authMethod = null;
        String authnContextStatementRef = null;
        String authnContextClassRef = null;
        Date authInstant = null;
        String securityDomain = null;
        Object token = null;
        String univId = null;
        SubjectLocality authLocality = null;

        FSSessionManager sessionManager = 
             FSSessionManager.getInstance(metaAlias);

        IDFFMetaManager metaManager = FSUtils.getIDFFMetaManager();
        Map attributes = new HashMap();
        if (metaManager != null) {
            BaseConfigType idpConfig = null;
            try {
                idpConfig = metaManager.getIDPDescriptorConfig(
                    realm, hostEntityId);
            } catch (IDFFMetaException e) {
                if (FSUtils.debug.messageEnabled()) {
                    FSUtils.debug.message(
                        "FSAssertionManager.createFSAssertion: exception while"
                        + " obtaining idp extended meta:", e);
                }
                idpConfig = null;
            }
            if (idpConfig != null) {
                attributes = IDFFMetaUtils.getAttributes(idpConfig);
            }
        }

        try {
            SessionProvider sessionProvider = SessionManager.getProvider();
            token = sessionProvider.getSession(id);
            String[] strAuthInst = null;
            try {
                strAuthInst = sessionProvider.getProperty(
                    token, SessionProvider.AUTH_INSTANT);
            } catch (UnsupportedOperationException ue) {
                if (FSUtils.debug.warningEnabled()) {
                    FSUtils.debug.warning(
                        "FSAssertionManager.createFSAssertion(id):", ue);
                }
            } catch (SessionException se) {
                if (FSUtils.debug.warningEnabled()) {
                    FSUtils.debug.warning(
                        "FSAssertionManager.createFSAssertion(id):", se);
                }
            }
            if ((strAuthInst != null) && (strAuthInst.length >= 1)){
                try {
                    authInstant = DateUtils.stringToDate(strAuthInst[0]);
                } catch(ParseException ex){
                    if (FSUtils.debug.messageEnabled()) {
                        FSUtils.debug.message("FSAssertionManager." + 
                            "createFSAssertion(id): AuthInstant not found" +
                            "in the Token");
                    }
                }
            } else {
                authInstant = newDate();
            }

            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "FSAssertionManager.createFSAssertion(id):AuthInstant = " 
                    + authInstant);
            }
            try {
                String[] strAuthMethod = sessionProvider.getProperty(
                    token, SessionProvider.AUTH_METHOD);
                if ((strAuthMethod != null) && (strAuthMethod.length >= 1)) {
                    authMethod = strAuthMethod[0];
                }
            } catch (UnsupportedOperationException ue) {
                if (FSUtils.debug.warningEnabled()) {
                    FSUtils.debug.warning(
                        "FSAssertionManager.createFSAssertion(id):", ue);
                }
            } catch (SessionException se) {
                if (FSUtils.debug.warningEnabled()) {
                    FSUtils.debug.warning(
                        "FSAssertionManager.createFSAssertion(id):", se);
                }
            }

            String assertionIssuer = IDFFMetaUtils.getFirstAttributeValue(
                attributes, IFSConstants.ASSERTION_ISSUER);
            if (assertionIssuer == null) {
                assertionIssuer = SystemConfigurationUtil.getProperty(
                        "com.iplanet.am.server.host");
            }

            try {
                String ipAddress = 
                    InetAddress.getByName(assertionIssuer).getHostAddress();
                authLocality = new SubjectLocality(ipAddress, assertionIssuer);
            } catch(UnknownHostException uhe) {
                FSUtils.debug.error("FSAssertionManager.constructor: couldn't"
                    + " obtain the localhost's ipaddress:", uhe);
            }
            
            try {
                FSSession session = sessionManager.getSession(token);
                authnContextClassRef = session.getAuthnContext();
                authnContextStatementRef = authnContextClassRef;
            } catch(Exception ex){
                if (FSUtils.debug.messageEnabled()) {
                    FSUtils.debug.message("FSAssertionManager.createFSAssertion"
                        + "(id): AuthnContextStatement for the token is null"
                        + " Assertion will not contain any "
                        + " AuthenticationStatement");
                }
                authnContextStatementRef = null;
            }
            if (authnContextStatementRef != null){
                if (assertionMinorVersion == 
                    IFSConstants.FF_11_ASSERTION_MINOR_VERSION) 
                {
                    authMethod = IFSConstants.AC_XML_NS;
                } else {
                    authMethod = IFSConstants.AC_12_XML_NS;
                }
            }
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "FSAssertionManager.createFSAssertion(id):"
                    + "AuthnContextStatement used for authenticating the user: "
                    +  authnContextStatementRef);
            }
            
            univId = sessionProvider.getPrincipalName(token);
            
            securityDomain = hostEntityId;
        } catch(Exception e) {
            FSUtils.debug.error("FSAssertionManager.createAssertion(id):"
                + " exception retrieving info from the session: ", e);
            throw new FSException(
                "alliance_manager_no_local_descriptor", null, e);
        }
        
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                + " Creating Authentication Assertion for user with"
                + "opaqueHandle= " 
                + spHandle.getName() 
                + " And SecurityDomain= " 
                + securityDomain);
        }
        SubjectConfirmation subConfirmation = null;
        String artString = null;
        if (artifact != null) {
            artString = artifact.getAssertionArtifact();
            if (assertionMinorVersion ==
                IFSConstants.FF_11_ASSERTION_MINOR_VERSION) 
            {
                subConfirmation = new SubjectConfirmation(
                    SAMLConstants.DEPRECATED_CONFIRMATION_METHOD_ARTIFACT);
            } else {
                subConfirmation = new SubjectConfirmation(
                    SAMLConstants.CONFIRMATION_METHOD_ARTIFACT);
            }
            subConfirmation.setSubjectConfirmationData(artString);
        } else {
            // set to bearer for POST profile
            subConfirmation = new SubjectConfirmation(
                SAMLConstants.CONFIRMATION_METHOD_BEARER);
        }
        IDPProvidedNameIdentifier idpNi = null;
        if (assertionMinorVersion == 
                IFSConstants.FF_12_POST_ASSERTION_MINOR_VERSION  ||
            assertionMinorVersion ==
                IFSConstants.FF_12_ART_ASSERTION_MINOR_VERSION) 
        {
            idpNi = new IDPProvidedNameIdentifier(
                    idpHandle.getName(),
                    idpHandle.getNameQualifier(), 
                    spHandle.getFormat());
            idpNi.setMinorVersion(IFSConstants.FF_12_PROTOCOL_MINOR_VERSION);
        } else {
            idpNi = new IDPProvidedNameIdentifier(idpHandle.getNameQualifier(),
                    idpHandle.getName());
        }
        
        FSSubject sub = new FSSubject(spHandle, subConfirmation, idpNi);
        AuthnContext authnContext = new AuthnContext(
            authnContextClassRef, authnContextStatementRef);
        authnContext.setMinorVersion(assertionMinorVersion);
        FSAuthenticationStatement statement = new FSAuthenticationStatement(
            authMethod, authInstant, sub, authLocality, null, authnContext);
        FSSession session = sessionManager.getSession(univId, id);
        
        if (session == null){
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.createAssertion(id): "
                    + "AssertionManager could not find a valid Session for"
                    + "userId: "
                    + univId + " SessionID: " + id);
            }
            return null;
        }
        
        String sessionIndex = session.getSessionIndex();
        if (sessionIndex == null) {
            sessionIndex = SAMLUtils.generateID();
            session.setSessionIndex(sessionIndex);
        }

        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message(
                "FSAssertionManager.createAssertion(id): SessionIndex: " +
                sessionIndex);
        }
        statement.setSessionIndex(sessionIndex);
        
        //setReauthenticateOnOrAfter date
        Date issueInstant = newDate();
        // get this period from the config
        
        FSUtils.debug.message("here before date");
        Date notAfter;
        if (artifact != null) {
            notAfter = new Date(issueInstant.getTime() + artifactTimeout);
        } else {
            notAfter = new Date(issueInstant.getTime() + assertionTimeout);
        }
        FSUtils.debug.message("here after date");
        statement.setReauthenticateOnOrAfter(notAfter);
        
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                + " Authentication Statement: " + statement.toXMLString());
        }
        
        Conditions cond = new Conditions(null, notAfter);
        if ((destID != null) &&(destID.length() != 0)) {
            List targets = new ArrayList();
            targets.add(destID);
            cond.addAudienceRestrictionCondition(
                new AudienceRestrictionCondition(targets));
        }
        
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                + " Authentication Statement: " + statement.toXMLString());
        }

        /**
         * This is added to create an attribute statement for the bootstrap
         * information.
         */
        AttributeStatement attribStatement =  null;
        Advice advice = null;
        String generateBootstrapping = IDFFMetaUtils.getFirstAttributeValue(
            attributes, IFSConstants.GENERATE_BOOTSTRAPPING);
        
        if (assertionMinorVersion != IFSConstants.FF_11_ASSERTION_MINOR_VERSION
            &&
            (generateBootstrapping != null &&
                generateBootstrapping.equals("true")))
        {
            AuthnContext authContext = new AuthnContext(
                null, authnContextStatementRef);
            authContext.setMinorVersion(
                IFSConstants.FF_12_PROTOCOL_MINOR_VERSION);
            try {
                FSDiscoveryBootStrap bootStrap = new FSDiscoveryBootStrap(
                    token, authContext, sub, univId, destID, realm);
                attribStatement = bootStrap.getBootStrapStatement();
                if (bootStrap.hasCredentials()) {
                    advice = bootStrap.getCredentials();
                }
            } catch (Exception e) {
                FSUtils.debug.error("FSAssertionManager.createAssertion(id):"
                    + "exception when generating bootstrapping resource "
                    + "offering:", e);
            }
        }

        AssertionIDReference aID = new AssertionIDReference();
        Set statements = new HashSet();
        statements.add(statement);
        if (attribStatement != null) {
            statements.add(attribStatement);
        }

        String attributePluginImpl = IDFFMetaUtils.getFirstAttributeValue(
            attributes, IFSConstants.ATTRIBUTE_PLUGIN);
        if ((attributePluginImpl != null) && 
            (attributePluginImpl.length() != 0)) 
        {
            try {
                Object pluginClass = 
                    Thread.currentThread().getContextClassLoader().loadClass(
                        attributePluginImpl).newInstance();
                List attribStatements = null;
                if (pluginClass instanceof FSRealmAttributePlugin) {
                    FSRealmAttributePlugin attributePlugin =
                        (FSRealmAttributePlugin)pluginClass;
                    attribStatements =
                        attributePlugin.getAttributeStatements(
                            realm, hostEntityId, destID, sub, token);
                } else if (pluginClass instanceof FSAttributePlugin) {
                    FSAttributePlugin attributePlugin =
                        (FSAttributePlugin)pluginClass;
                    attribStatements =
                        attributePlugin.getAttributeStatements(
                            hostEntityId, destID, sub, token);
                }

                if ((attribStatements != null) && 
                    (attribStatements.size() != 0)) 
                {
                    Iterator iter = attribStatements.iterator();
                    while (iter.hasNext()) {
                        statements.add((AttributeStatement)iter.next()); 
                    }
                }
            } catch (Exception ex) {
                FSUtils.debug.error(
                    "FSAssertion.createAssertion(id):getAttributePlugin:", ex);
            }
        }

        if (IDFFMetaUtils.isAutoFedEnabled(attributes)) {
            AttributeStatement autoFedStatement = 
                FSAttributeStatementHelper.getAutoFedAttributeStatement(
                    realm, hostEntityId, sub, token);
            statements.add(autoFedStatement);
        }

        FSAssertion assertion = new FSAssertion(aID.getAssertionIDReference(), 
                                                hostEntityId, 
                                                issueInstant, 
                                                cond, 
                                                advice,
                                                statements, 
                                                inResponseTo);
        assertion.setMinorVersion(assertionMinorVersion);
        assertion.setID(aID.getAssertionIDReference());
        
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                + " Assertion created successfully: " 
                + assertion.toXMLString());
        }


        String aIDString = assertion.getAssertionID();
        Entry entry = new Entry(assertion, destID, artString, token);
        Integer maxNumber = null; 
        try {
            int temp = Integer.parseInt(IDFFMetaUtils.getFirstAttributeValue(
                attributes, IFSConstants.ASSERTION_LIMIT));
            maxNumber = new Integer(temp);
        } catch(Exception ex){
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                    + " Assertion MAX number configuration not found in "
                    + "FSConfig. Using Default");
            }
            maxNumber = null;
        }
        if (maxNumber == null) {
            maxNumber = new Integer(IFSConstants.ASSERTION_MAX_NUMBER_DEFAULT);
        }
        
        int maxValue = maxNumber.intValue();
        if ((maxValue != 0) &&(idEntryMap.size() > maxValue)) {
            FSUtils.debug.error("FSAssertionManager.createAssertion: "
                + "reached maxNumber of assertions.");
            throw new FSException("errorCreateAssertion", null);
        }
        Object oldEntry = null;
        try {
            synchronized(idEntryMap) {
                oldEntry = idEntryMap.put(aIDString, entry);
            }
            if ((agent != null) && agent.isRunning() && (idffSvc != null)) {
                idffSvc.setAssertions((long)idEntryMap.size());
            }
        } catch(Exception e) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager: couldn't add "
                    + "to idEntryMap.", e);
            }
            throw new FSException("errorCreateAssertion",null);
        }
        if (LogUtil.isAccessLoggable(Level.FINER)) {
            String[] data = { assertion.toString() };
            LogUtil.access(Level.FINER,LogUtil.CREATE_ASSERTION,data, token);
        } else {
            String[] data = { assertion.getAssertionID() } ;
            LogUtil.access(Level.INFO,LogUtil.CREATE_ASSERTION,data, token);
        }
        if (artString != null) {
            try {
                synchronized(artIdMap) {
                    oldEntry = artIdMap.put(artString, aIDString);
                }
                if ((agent != null) && agent.isRunning() && (idffSvc != null)){
                    idffSvc.setArtifacts((long)artIdMap.size());
                }
            } catch(Exception e) {
                if (FSUtils.debug.messageEnabled()) {
                    FSUtils.debug.message("FSAssertionManager: couldn't add "
                    + "artifact to the artIdMap.", e);
                }
                throw new FSException("errorCreateArtifact",null);
            }
            if (oldEntry != null) {
                artifactTimeoutRunnable.removeElement(aIDString);
            }
            artifactTimeoutRunnable.addElement(aIDString);
        } else {
            if (oldEntry != null) {
                assertionTimeoutRunnable.removeElement(aIDString);
            }
            assertionTimeoutRunnable.addElement(aIDString);
        }
        
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("FSAssertionManager.createAssertion(id):"
                + " Returning Assertion: " + assertion.toXMLString());
        }
        return assertion;
    }
    
    /**
     * Retrieves the assertion associated with an artifact.
     * @param artifact assertion artifact
     * @param destID destination ID of the site who sent the request
     * @return assertion associated with the artifact
     * @exception FSException if the assertion could not be retrieved
     */
    public Assertion getAssertion(
        AssertionArtifact artifact,
        String destID
    ) throws FSException 
    {
        if ((artifact == null) ||
            (destID == null || destID.length() == 0))
        {
            FSUtils.debug.message("FSAssertionManager: input is null.");
            throw new FSException("nullInput",null);
        }
        String artString = artifact.getAssertionArtifact();
        // get server id.
        String remoteUrl = SAMLUtils.getServerURL(
                                        artifact.getAssertionHandle());
        if (remoteUrl != null) { // not this server
            // call AssertionManagerClient.getAssertion
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("AssertionManager.getAssertion(art, " +
                    "destid: calling another server in lb site:" + remoteUrl);
            }
            FSAssertionManagerClient amc = new FSAssertionManagerClient(
                metaAlias, getFullServiceURL(remoteUrl));
            return amc.getAssertion(artifact, destID);
        } // else 
        
        String aIDString = null;
        
        try {
            aIDString = (String) artIdMap.get(artString);
            if (aIDString == null) {
                throw new FSException("nullInput",null);
            }
        } catch(Exception e) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.getAssertion(art, de"
                    + "stid): no AssertionID found corresponding to artifact.");
            }
            throw new FSException("noMatchingAssertion",null);
        }
        
        Entry entry = null;
        try {
            entry =(Entry) idEntryMap.get(aIDString);
            if (entry == null) {
                throw new FSException("nullEntry",null);
            }
        } catch(Exception e) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.getAssertion(art, de"
                + "stid): no Entry found corresponding to artifact.");
            }
            throw new FSException("noMatchingAssertion", null);
        }

        // check the destination id
        String dest = entry.getDestID();
        if (dest == null) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.getAssertion(art, de"
                    + "stid): no destID found corresponding to artifact.");
            }
            throw new FSException("noDestIDMatchingArtifact",null);
        }
        if (!dest.equals(destID)) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.getAssertion(art, de"
                    + "stid): destinationID doesn't match.");
            }
            throw new FSException("destIDNotMatch",null);
        }
        
        synchronized(artIdMap) {
            artIdMap.remove(artString);
        }
        if ((agent != null) && agent.isRunning() && (idffSvc != null)) {
            idffSvc.setArtifacts((long)artIdMap.size());
        }
        artifactTimeoutRunnable.removeElement(aIDString);
        synchronized(idEntryMap) {
            idEntryMap.remove(aIDString);
        }
        if ((agent != null) && agent.isRunning() && (idffSvc != null)) {
            idffSvc.setAssertions((long)idEntryMap.size());
        }
        assertionTimeoutRunnable.removeElement(aIDString);
       
        Assertion assertion = entry.getAssertion();
        if (assertion == null) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager.getAssertion(art, de"
                    + "stid): no Assertion found corresponding to aID.");
            }
            throw new FSException("noMatchingAssertion",null);
        }

        if (!assertion.isTimeValid()) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("FSAssertionManager: assertion "
                    + aIDString + " is expired.");
            }
            throw new FSException("assertionTimeNotValid",null);
        }
        return assertion;
    }
    
    /**
     * Finds the destination id for whom the artifact is issued for.
     * @param artifact assertion artifact
     * @return destination id
     * @exception FSException if error occurrs
     */
    public String getDestIdForArtifact(
        AssertionArtifact artifact
    ) throws FSException
    {
        FSUtils.debug.message(
            "FSAssertionManager.getDestIdForArtifact: Called");
        String artString = artifact.getAssertionArtifact();
        
        // get server id.
        String remoteUrl = SAMLUtils.getServerURL(
                                        artifact.getAssertionHandle());
        if (remoteUrl != null) { // not this server
            // call FSAssertionManagerClient.getAssertion
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "AssertionManager.getDestIdForArtifact(art, " +
                    "destid: calling another server in lb site:" + remoteUrl);
            }
            FSAssertionManagerClient amc = new FSAssertionManagerClient(
                metaAlias, getFullServiceURL(remoteUrl));
            return amc.getDestIdForArtifact(artifact);
        } // else 
        
        String aIDString = null;
        
        try {
            aIDString =(String) artIdMap.get(artString);
            if (aIDString == null) {
                throw new FSException("nullInput",null);
            }
        } catch(Exception e) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "FSAssertionManager.getDestIdForArtifact :" +
                    "no AssertionID found corresponding to artifact.");
            }
            throw new FSException("noMatchingAssertion",null);
        }
        
        Entry entry = null;
        try {
            entry =(Entry) idEntryMap.get(aIDString);
            if (entry == null) {
                throw new FSException("nullEntry",null);
            }
        } catch(Exception e) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "FSAssertionManager.getDestIdForArtifact: " +
                    "no Entry found corresponding to artifact.");
            }
            throw new FSException("noMatchingAssertion",null);
        }
        
        String dest = entry.getDestID();
        if (dest == null) {
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "FSAssertionManager.getDestIdForArtifact: " +
                    "no destID found corresponding to artifact.");
            }
            throw new FSException("noDestIDMatchingArtifact",null);
        }
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message(
                "FSAssertionManager.getDestIdForArtifact: " +
                "Destination ProviderID found for Artifact: " + dest);
        }
        return dest;
    }

    private String getFullServiceURL(String shortUrl) {
        String result = null;
        try {
            URL u = new URL(shortUrl);
            URL weburl = SystemConfigurationUtil.getServiceURL(
                SERVICE_NAMING, u.getProtocol(), u.getHost(), u.getPort(),
                u.getPath());
            result = weburl.toString();
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message(
                    "AssertionManager.getFullServiceURL:full remote URL is: " +
                    result);
            }
        } catch (Exception e) {
            if (FSUtils.debug.warningEnabled()) {
                FSUtils.debug.warning(
                    "AssertionManager.getFullServiceURL:Exception:", e);
            }
        }
        return result;
    }
    
    /**
     * Specific assertion class containing date and status.
     * This is used to store information about a fault artifact.
     */
    class ErrorAssertion extends Assertion {
        private Status _status;
        public ErrorAssertion( java.util.Date issue, Status status ) {
            _issueInstant = issue;
            _status = status;
        }
        public Status getStatus() {
            return _status;
        }
        
    }
    
    /**
     * Store the status of a given artifact (original error)
     * @param aa reference artifact
     * @param s stored status
     */
    public void setErrStatus( AssertionArtifact aa, Status s ) {
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("setErrStatus( " + aa + ", " + s + " )" );
        }
        
        String artString = aa.getAssertionArtifact();
        Assertion assertion = new ErrorAssertion(newDate(), s);
        Entry e = new Entry( assertion, null, artString, null );
        Object oldEntry = null;
        synchronized (idEntryMap) {
            oldEntry = idEntryMap.put( artString, e );
        }
        if (oldEntry != null) {
            assertionTimeoutRunnable.removeElement(artString);
            if ((agent != null) && agent.isRunning() && (idffSvc != null)) {
                idffSvc.setAssertions((long)idEntryMap.size());
            }
        }
        assertionTimeoutRunnable.addElement(artString);
    }
    /**
     * Retrieve the original status of a reference artifact.
     * @param aa reference artifact
     * @return The status as originally recorded.
     */
    public Status getErrorStatus( AssertionArtifact aa ) {
        if (FSUtils.debug.messageEnabled()) {
            FSUtils.debug.message("getErrorStatus( " + aa + " )" );
        }
        Entry e = null;
        Status s = null;
        String remoteUrl = SAMLUtils.getServerURL(
                aa.getAssertionHandle());
        if (remoteUrl != null) { // not this server
            // call AssertionManagerClient.getAssertion
            if (FSUtils.debug.messageEnabled()) {
                FSUtils.debug.message("AssertionManager.getAssertion(art, " 
                        + "destid: calling another server in lb site:" 
                        + remoteUrl);
            }
            try {
                FSAssertionManagerClient amc = new FSAssertionManagerClient(
                    metaAlias, getFullServiceURL(remoteUrl));
                s = amc.getErrorStatus( aa );
            } catch ( FSException fse ) {
                FSUtils.debug.error( fse.getMessage() );
            }
        } else { 
            e = (Entry) idEntryMap.get( aa.getAssertionArtifact() );
            if ( null != e ) {
                Assertion assertion = e.getAssertion();
                if ( assertion instanceof ErrorAssertion ) {
                    ErrorAssertion eassert = (ErrorAssertion) assertion;
                    s = eassert.getStatus();
                }
            }
        }
        return s;
    }
}
