/**
 * 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: SessionService.java,v 1.37 2010/02/03 03:52:54 bina Exp $
 *
 * Portions Copyrighted 2010-2016 ForgeRock AS.
 */

package com.iplanet.dpro.session.service;

import static com.iplanet.dpro.session.service.SessionBroadcastMode.*;
import static com.iplanet.dpro.session.service.SessionConstants.*;
import static com.sun.identity.shared.Constants.*;
import static org.forgerock.openam.cts.api.CoreTokenConstants.*;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.forgerock.openam.session.stateless.cache.StatelessJWTCache;
import org.forgerock.openam.sso.providers.stateless.JwtSessionMapper;
import org.forgerock.openam.sso.providers.stateless.JwtSessionMapperConfig;

import com.iplanet.am.util.SystemProperties;
import com.iplanet.dpro.session.service.cluster.ClusterStateService;
import com.iplanet.services.naming.ServiceListeners;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.common.configuration.SiteConfiguration;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.datastruct.CollectionHelper;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.SMSException;
import com.sun.identity.sm.ServiceConfig;
import com.sun.identity.sm.ServiceConfigManager;
import com.sun.identity.sm.ServiceSchema;
import com.sun.identity.sm.ServiceSchemaManager;

/**
 * Responsible for collating System Properties and amSession.xml configuration state relating to the Session Service.
 *
 * @since 13.0.0
 */
@Singleton
public class SessionServiceConfig {

    public static final String AM_SESSION_SERVICE_NAME = "iPlanetAMSessionService";

    private final Debug sessionDebug;
    private final SessionServerConfig sessionServerConfig;
    private final PrivilegedAction<SSOToken> dsameAdminTokenProvider;

    /*
     * Constant Properties
     */

    private final String HTTP_SESSION_PROPERTY_NAME = "DSAMEInternalSession";
    private final String HTTP_SESSION_OWNER_LIST_PROPERTY_NAME = "DSAMEInternalSession.ownerList";
    private final String SECURITY_COOKIE_NAME = "DSAMESecurityCookie";

    /*
     * System Properties
     */

    /**
     * Property string for max number of sessions
     */
    static final int DEFAULT_MAX_SESSIONS = 10000;
    private final int maxSessions;

    private static final String LOGSTATUS_ACTIVE = "ACTIVE";
    private final boolean logStatus;

    private static final String DEFAULT_HTTP_SESSION_TRACKING_COOKIE_NAME = "JSESSIONID";
    private final String httpSessionTrackingCookieName;

    private static final boolean DEFAULT_COOKIE_ENCODING = false;
    private final boolean cookieEncoding;

    private static final int DEFAULT_NOTIFICATION_THEAD_POOL_SIZE = 10;
    private final int notificationThreadPoolSize;

    private static final int DEFAULT_NOTIFICATION_THEAD_POOL_THRESHOLD = DEFAULT_NOTIFICATION_THEAD_POOL_SIZE * 10;
    private final int notificationThreadPoolThreshold;

    private static final long DEFAULT_APPLICATION_MAX_CACHING_TIME = Long.MAX_VALUE / 60;
    private final long applicationMaxCachingTime;

    private static final boolean DEFAULT_RETURN_APP_SESSION = false;
    private final boolean returnAppSession;

    // Must be True to permit Session Failover HA to be available.
    private static final boolean DEFAULT_USE_REMOTE_SAVE_METHOD = true;
    private boolean useRemoteSaveMethod;

    // Must be True to permit Session Failover HA to be available.
    private static final boolean DEFAULT_USE_INTERNAL_REQUEST_ROUTING = true;
    private boolean useInternalRequestRouting;

    // Must be True to permit Session Failover HA to be available, but we default this to Disabled or Off for Now.
    private static final boolean DEFAULT_SESSION_FAILOVER_ENABLED = false;
    private volatile boolean sessionFailoverEnabled;

    private final int sessionFailoverClusterStateCheckTimeout;
    private final long sessionFailoverClusterStateCheckPeriod;

    /*
     * amSession.xml (SMS) Properties
     */

    private volatile HotSwappableSessionServiceConfig hotSwappableSessionServiceConfig;

    /**
     * Indicates whether to use crosstalk or session persistence to resolve remote sessions.
     * Always true when session persistence/SFO is disabled.
     */
    private static final boolean DEFAULT_REDUCED_CROSSTALK_ENABLED = true;
    private volatile boolean reducedCrosstalkEnabled = DEFAULT_REDUCED_CROSSTALK_ENABLED;

    /**
     * The number of minutes to retain {@link com.iplanet.dpro.session.Session} objects in DESTROYED state
     * while waiting for delete replication to occur if reduced cross-talk is enabled.
     */
    private static final long DEFAULT_REDUCED_CROSSTALK_PURGE_DELAY = 5;
    private volatile long reducedCrosstalkPurgeDelay = DEFAULT_REDUCED_CROSSTALK_PURGE_DELAY;

    /**
     * Indicates what broadcast to undertake on session logout/destroy
     */
    private static final SessionBroadcastMode DEFAULT_LOGOUT_DESTROY_BROADCAST = OFF;
    private volatile SessionBroadcastMode logoutDestroyBroadcast = DEFAULT_LOGOUT_DESTROY_BROADCAST;

    /**
     * Private value object for storing snapshot state of amSession.xml config settings.
     *
     * This allows immutable value objects to be published as an atomic operation.
     */
    private class HotSwappableSessionServiceConfig {

        private static final long DEFAULT_SESSION_RETRIEVAL_TIMEOUT = 5;
        private static final int DEFAULT_MAX_SESSION_LIST_SIZE = 200;
        private static final int DEFAULT_MAX_WAIT_TIME_FOR_CONSTRAINT = 6000;

        private final JwtSessionMapperConfig jwtSessionMapperConfig;
        private final Set<String> timeoutHandlers;
        private final boolean sessionTrimmingEnabled;
        private final boolean sessionConstraintEnabled;
        private final boolean denyLoginIfDBIsDown;
        private final String constraintHandler;
        private final boolean propertyNotificationEnabled;
        private final Set notificationProperties;
        private final long sessionRetrievalTimeout; //  in seconds
        private final int maxSessionListSize;
        private final int maxWaitTimeForConstraint; // in milli-seconds

        private final boolean sessionBlacklistEnabled;
        private final int sessionBlacklistCacheSize;
        private final long sessionBlacklistPollIntervalSeconds;
        private final long sessionBlacklistPurgeDelayMinutes;


        private HotSwappableSessionServiceConfig(ServiceSchemaManager ssm) throws SMSException {
            ServiceSchema schema = ssm.getGlobalSchema();
            Map attrs = schema.getAttributeDefaults();

            jwtSessionMapperConfig = new JwtSessionMapperConfig(attrs);
            sessionRetrievalTimeout = loadSessionRetrievalTimeoutSchemaSetting(attrs);
            maxSessionListSize = loadMaxSessionListSizeSchemaSetting(attrs);
            propertyNotificationEnabled = loadPropertyNotificationEnabledSchemaSetting(attrs);
            notificationProperties = loadPropertyNotificationPropertiesSchemaSetting(attrs);
            timeoutHandlers = loadTimeoutHandlersServiceSchemaSetting(attrs);
            sessionTrimmingEnabled = loadSessionTrimmingServiceSchemaSetting(attrs);
            sessionConstraintEnabled = loadSessionConstraintServiceSchemaSetting(attrs);
            denyLoginIfDBIsDown = loadDenyLoginIfDBIsDownServiceSchemaSetting(attrs);
            constraintHandler = loadConstraintHandlerServiceSchemaSetting(attrs);
            maxWaitTimeForConstraint = loadMaxWaitTimeForConstraintHandlerServiceSchemaSetting(attrs);

            sessionBlacklistEnabled = CollectionHelper.getBooleanMapAttr(attrs, SESSION_BLACKLIST_ENABLED_ATTR, false);
            sessionBlacklistCacheSize = CollectionHelper.getIntMapAttr(attrs, SESSION_BLACKLIST_CACHE_SIZE_ATTR, 0,
                    sessionDebug);
            sessionBlacklistPollIntervalSeconds = CollectionHelper.getLongMapAttr(attrs,
                    SESSION_BLACKLIST_POLL_INTERVAL_ATTR, 60, sessionDebug);
            sessionBlacklistPurgeDelayMinutes = CollectionHelper.getLongMapAttr(attrs,
                    SESSION_BLACKLIST_PURGE_DELAY_ATTR, 1, sessionDebug);
        }

        public boolean isSendPropertyNotification(String key) {
            return propertyNotificationEnabled && notificationProperties.contains(key);
        }

        private long loadSessionRetrievalTimeoutSchemaSetting(Map attrs) {
            long value = TimeUnit.SECONDS.toMillis(CollectionHelper.getLongMapAttr(
                    attrs, AM_SESSION_SESSION_LIST_RETRIEVAL_TIMEOUT, DEFAULT_SESSION_RETRIEVAL_TIMEOUT, sessionDebug));
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("sessionRetrievalTimeout=" + value);
            }
            return value;
        }

        private int loadMaxSessionListSizeSchemaSetting(Map attrs) {
            int value = CollectionHelper.getIntMapAttr(
                    attrs, AM_SESSION_MAX_SESSION_LIST_SIZE, DEFAULT_MAX_SESSION_LIST_SIZE, sessionDebug);
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("maxSessionListSize=" + value);
            }
            return value;
        }

        private boolean loadPropertyNotificationEnabledSchemaSetting(Map attrs) {
            boolean value = "ON".equalsIgnoreCase(
                    CollectionHelper.getMapAttr(attrs, Constants.PROPERTY_CHANGE_NOTIFICATION));
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("value=" + value);
            }
            return value;
        }

        private Set loadPropertyNotificationPropertiesSchemaSetting(Map attrs) {
            Set value = null;
            if (propertyNotificationEnabled) {
                value = (Set) attrs.get(Constants.NOTIFICATION_PROPERTY_LIST);
            }
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("notificationProperties=" + value);
            }
            return value;
        }

        private Set<String> loadTimeoutHandlersServiceSchemaSetting(Map attrs) {
            Set<String> values = (Set<String>) attrs.get(Constants.TIMEOUT_HANDLER_LIST);
            if (values == null) {
                values = Collections.emptySet();
            } else {
                // Only copy non-empty String values to avoid triggering a ClassNotFoundException on empty values when
                // SessionService iterates over the list to call the handlers.
                Set<String> valuesCopy = new HashSet<String>(values.size());
                for (String value : values) {
                    if (!value.isEmpty()) {
                        valuesCopy.add(value);
                    }
                }
                values = valuesCopy;
            }
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("timeoutHandlers=" + values);
            }
            return values;
        }

        private boolean loadSessionTrimmingServiceSchemaSetting(Map attrs) {
            boolean value = "YES".equalsIgnoreCase(
                    CollectionHelper.getMapAttr(attrs, Constants.ENABLE_TRIM_SESSION));
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("sessionTrimmingEnabled=" + value);
            }
            return value;
        }

        private boolean loadSessionConstraintServiceSchemaSetting(Map attrs) {
            boolean value = "ON".equalsIgnoreCase(
                    CollectionHelper.getMapAttr(attrs, AM_SESSION_ENABLE_SESSION_CONSTRAINT));
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("sessionConstraintEnabled=" + value);
            }
            return value;
        }

        private boolean loadDenyLoginIfDBIsDownServiceSchemaSetting(Map attrs) {
            boolean value = "YES".equalsIgnoreCase(
                    CollectionHelper.getMapAttr(attrs, AM_SESSION_DENY_LOGIN_IF_DB_IS_DOWN));
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("denyLoginIfDBIsDown=" + value);
            }
            return value;
        }

        private String loadConstraintHandlerServiceSchemaSetting(Map attrs) {
            String value = CollectionHelper.getMapAttr(
                    attrs, AM_SESSION_CONSTRAINT_HANDLER, SessionConstraint.DESTROY_OLDEST_SESSION_CLASS);
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("Resulting behavior if session quota exhausted:" + value);
            }
            return value;
        }

        private int loadMaxWaitTimeForConstraintHandlerServiceSchemaSetting(Map attrs) {
            int value = CollectionHelper.getIntMapAttr(
                    attrs, AM_SESSION_CONSTRAINT_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME_FOR_CONSTRAINT, sessionDebug);
            if (sessionDebug.messageEnabled()) {
                sessionDebug.message("maxWaitTimeForConstraint=" + maxWaitTimeForConstraint);
            }
            return value;
        }
    }

    @Inject
    SessionServiceConfig(
            @Named(SessionConstants.SESSION_DEBUG) final Debug sessionDebug,
            SessionServerConfig sessionServerConfig,
            PrivilegedAction<SSOToken> adminTokenProvider,
            final StatelessJWTCache jwtCache,
            final ServiceListeners serviceListeners) {

        this.sessionDebug = sessionDebug;
        this.sessionServerConfig = sessionServerConfig;
        this.dsameAdminTokenProvider = adminTokenProvider;

        // Initialize values set from System properties
        maxSessions =
                SystemProperties.getAsInt(AM_SESSION_MAX_SESSIONS, DEFAULT_MAX_SESSIONS);
        logStatus =
                LOGSTATUS_ACTIVE.equalsIgnoreCase(SystemProperties.get(AM_LOGSTATUS));
        httpSessionTrackingCookieName =
                SystemProperties.get(AM_SESSION_HTTP_SESSION_TRACKING_COOKIE_NAME, DEFAULT_HTTP_SESSION_TRACKING_COOKIE_NAME);
        cookieEncoding =
                SystemProperties.getAsBoolean(AM_COOKIE_ENCODE, DEFAULT_COOKIE_ENCODING);
        notificationThreadPoolSize =
                loadNotificationThreadPoolSizeSystemProperty();
        notificationThreadPoolThreshold =
                loadNotificationThreadPoolThresholdSystemProperty();
        applicationMaxCachingTime =
                SystemProperties.getAsLong(APPLICATION_SESSION_MAX_CACHING_TIME, DEFAULT_APPLICATION_MAX_CACHING_TIME);
        returnAppSession =
                SystemProperties.getAsBoolean(SESSION_RETURN_APP_SESSION, DEFAULT_RETURN_APP_SESSION);
        sessionFailoverClusterStateCheckTimeout =
                loadSessionFailoverClusterStateCheckTimeout();
        sessionFailoverClusterStateCheckPeriod =
                loadSessionFailoverClusterStateCheckPeriod();

        try {

            // Initialize settings from SMS

            final ServiceSchemaManager serviceSchemaManager =
                    new ServiceSchemaManager(AM_SESSION_SERVICE_NAME, AccessController.doPrivileged(adminTokenProvider));
            hotSwappableSessionServiceConfig = new HotSwappableSessionServiceConfig(serviceSchemaManager);

            ServiceListeners.Action action = new ServiceListeners.Action() {
                @Override
                public void performUpdate() {
                    try {
                        loadSessionFailover();
                        hotSwappableSessionServiceConfig = new HotSwappableSessionServiceConfig(serviceSchemaManager);
                    } catch (SSOException | SMSException e) {
                        throw new IllegalStateException(e);
                    }
                }
            };
            serviceListeners.config(AM_SESSION_SERVICE_NAME)
                    .global(action)
                    .schema(action).listen();

            loadSessionFailover();

        } catch (Exception ex) {
            sessionDebug.error("SessionService: Initialization Failed", ex);
            // Rethrow exception rather than hobbling on with invalid configuration state
            throw new IllegalStateException("Failed to load SessionService configuration", ex);
        }
    }

    private synchronized void loadSessionFailover() throws SSOException, SMSException {
        useRemoteSaveMethod = SystemProperties.getAsBoolean(AM_SESSION_FAILOVER_USE_REMOTE_SAVE_METHOD, DEFAULT_USE_REMOTE_SAVE_METHOD);
        useInternalRequestRouting = SystemProperties.getAsBoolean(AM_SESSION_FAILOVER_USE_INTERNAL_REQUEST_ROUTING, DEFAULT_USE_INTERNAL_REQUEST_ROUTING);
        sessionFailoverEnabled = SystemProperties.getAsBoolean(IS_SFO_ENABLED, DEFAULT_SESSION_FAILOVER_ENABLED);

        SSOToken adminToken = AccessController.doPrivileged(dsameAdminTokenProvider);

        ServiceConfigManager scm = new ServiceConfigManager(AM_SESSION_SERVICE_NAME, adminToken);
        ServiceConfig serviceConfig = scm.getGlobalConfig(null);

            /*
             * In OpenSSO 8.0, we have switched to create sub configuration with
             * site name. hence we need to lookup the site name based on the URL
             */
        String subCfgName = SiteConfiguration.getSiteIdByURL(adminToken, sessionServerConfig.getPrimaryServerURL().toString());
        ServiceConfig subConfig = subCfgName != null ? serviceConfig.getSubConfig(subCfgName) : null;

        if ((subConfig != null) && subConfig.exists()) {

            Map sessionAttrs = subConfig.getAttributes();
            boolean sfoEnabled = CollectionHelper.getBooleanMapAttr(sessionAttrs, IS_SFO_ENABLED, false);

            // Currently, we are not allowing to default to Session Failover HA,
            // even with a single server to enable session persistence.
            // But can easily be turned on in the Session SubConfig.
            if (sfoEnabled) {

                sessionFailoverEnabled = true;
                useRemoteSaveMethod = true;
                useInternalRequestRouting = true;

                // Determine whether crosstalk is enabled or disabled.
                reducedCrosstalkEnabled =
                        CollectionHelper.getBooleanMapAttr(sessionAttrs, IS_REDUCED_CROSSTALK_ENABLED, true);

                if (reducedCrosstalkEnabled) {
                    logoutDestroyBroadcast = valueOf(
                            CollectionHelper.getMapAttr(
                                    sessionAttrs, LOGOUT_DESTROY_BROADCAST, OFF.name()));
                }

                reducedCrosstalkPurgeDelay =
                        CollectionHelper.getLongMapAttr(sessionAttrs,
                                REDUCED_CROSSTALK_PURGE_DELAY, 1, sessionDebug);

            }
        }

        if (sessionDebug.messageEnabled()) {
            sessionDebug.message("Session Failover Enabled = " + sessionFailoverEnabled);
        }
    }

    private int loadNotificationThreadPoolSizeSystemProperty() {
        String size = SystemProperties.get(NOTIFICATION_THREADPOOL_SIZE);
        try {
            return Integer.parseInt(size);
        } catch (NumberFormatException e) {
            sessionDebug.error(
                    "Invalid value for " + NOTIFICATION_THREADPOOL_SIZE +
                            " defaulting to " + DEFAULT_NOTIFICATION_THEAD_POOL_SIZE);
            return DEFAULT_NOTIFICATION_THEAD_POOL_SIZE;
        }
    }

    private int loadNotificationThreadPoolThresholdSystemProperty() {
        String threshold = SystemProperties.get(NOTIFICATION_THREADPOOL_THRESHOLD);
        try {
            return Integer.parseInt(threshold);
        } catch (NumberFormatException e) {
            sessionDebug.error(
                    "Invalid value for " + NOTIFICATION_THREADPOOL_THRESHOLD +
                            " defaulting to " + DEFAULT_NOTIFICATION_THEAD_POOL_THRESHOLD);
            return DEFAULT_NOTIFICATION_THEAD_POOL_THRESHOLD;
        }
    }

    private int loadSessionFailoverClusterStateCheckTimeout() {
        String timeout = SystemProperties.get(AM_SESSION_FAILOVER_CLUSTER_STATE_CHECK_TIMEOUT);
        try {
            return Integer.parseInt(timeout);
        } catch (Exception e) {
            sessionDebug.error(
                    "Invalid value for " + Constants.AM_SESSION_FAILOVER_CLUSTER_STATE_CHECK_TIMEOUT +
                            " defaulting to " + ClusterStateService.DEFAULT_TIMEOUT);
            return ClusterStateService.DEFAULT_TIMEOUT;
        }
    }

    private long loadSessionFailoverClusterStateCheckPeriod() {
        String period = SystemProperties.get(AM_SESSION_FAILOVER_CLUSTER_STATE_CHECK_PERIOD);
        try {
            return Long.parseLong(period);
        } catch (Exception e) {
            sessionDebug.error(
                    "Invalid value for " + Constants.AM_SESSION_FAILOVER_CLUSTER_STATE_CHECK_PERIOD +
                            " defaulting to " + ClusterStateService.DEFAULT_PERIOD);
            return ClusterStateService.DEFAULT_PERIOD;
        }
    }

    /**
     * Returns true if SystemProperty "com.iplanet.am.session.failover.useRemoteSaveMethod" is true or
     * Session Failover is enabled.
     *
     * @see #isSessionFailoverEnabled()
     */
    public boolean isUseRemoteSaveMethod() {
        return useRemoteSaveMethod;
    }

    /**
     * Returns amSession.xml property "iplanet-am-session-logout-destroy-broadcast" choice.
     *
     * Defaults to {@link SessionBroadcastMode#OFF }.
     */
    public SessionBroadcastMode getLogoutDestroyBroadcast() {
        return logoutDestroyBroadcast;
    }

    /**
     * Returns amSession.xml property "iplanet-am-session-constraint-handler".
     *
     * This should be the fully qualified name of a class implementing
     * {@link com.iplanet.dpro.session.service.QuotaExhaustionAction}.
     *
     * Defaults to {@link org.forgerock.openam.session.service.DestroyOldestAction}.
     *
     * @see com.iplanet.dpro.session.service.QuotaExhaustionAction
     * @see org.forgerock.openam.session.service.DestroyOldestAction
     */
    public String getConstraintHandler() {
        return hotSwappableSessionServiceConfig.constraintHandler;
    }

    /**
     * Returns true if amSession.xml property "iplanet-am-session-deny-login-if-db-is-down" is "YES" (case insensitive).
     *
     * Defaults to false.
     */
    public boolean isDenyLoginIfDBIsDown() {
        return hotSwappableSessionServiceConfig.denyLoginIfDBIsDown;
    }

    /**
     * Returns true if amSession.xml property "iplanet-am-session-enable-session-constraint" is "ON" (case insensitive).
     *
     * Defaults to false.
     */
    public boolean isSessionConstraintEnabled() {
        return hotSwappableSessionServiceConfig.sessionConstraintEnabled;
    }

    /**
     * Returns true if amSession.xml property "iplanet-am-session-enable-session-trimming" is "YES" (case insensitive).
     *
     * Defaults to false.
     */
    public boolean isSessionTrimmingEnabled() {
        return hotSwappableSessionServiceConfig.sessionTrimmingEnabled;
    }

    /**
     * The number of minutes to retain {@link com.iplanet.dpro.session.Session} objects in DESTROYED state while waiting
     * for delete replication to occur if reduced cross-talk is enabled.
     */
    public long getReducedCrosstalkPurgeDelay() {
        return reducedCrosstalkPurgeDelay;
    }

    public String getHttpSessionPropertyName() {
        return HTTP_SESSION_PROPERTY_NAME;
    }

    public String getHttpSessionOwnerListPropertyName() {
        return HTTP_SESSION_OWNER_LIST_PROPERTY_NAME;
    }

    public String getSecurityCookieName() {
        return SECURITY_COOKIE_NAME;
    }

    /**
     * Returns SystemProperty "com.iplanet.am.session.maxSessions".
     *
     * Defaults to 10,000 if not specified.
     */
    public int getMaxSessions() {
        return maxSessions;
    }

    /**
     * Returns true if SystemProperty "com.iplanet.am.logstatus" is "ACTIVE" (case insensitive).
     *
     * Defaults to false.
     */
    public boolean isLoggingEnabled() {
        return logStatus;
    }

    /**
     * Returns the name of the cookie/URL parameter used by J2EE container for
     * session tracking (currently hardcoded to "JSESSIONID")
     */
    public String getHttpSessionTrackingCookieName() {
        return httpSessionTrackingCookieName;
    }

    /**
     * Returns true if SystemProperty "com.iplanet.am.cookie.encode" is true.
     *
     * Defaults to false.
     */
    public boolean isCookieEncodingEnabled() {
        return cookieEncoding;
    }

    /**
     * Returns value of SystemProperty "com.iplanet.am.notification.threadpool.size".
     *
     * Defaults to 10 if not specified.
     */
    public int getNotificationThreadPoolSize() {
        return notificationThreadPoolSize;
    }

    /**
     * Returns value of SystemProperty "com.iplanet.am.notification.threadpool.threshold".
     *
     * Defaults to 100 if not specified.
     */
    public int getNotificationThreadPoolThreshold() {
        return notificationThreadPoolThreshold;
    }

    /**
     * Returns value of SystemProperty "com.sun.identity.session.returnAppSession".
     *
     * Defaults to false.
     */
    public boolean isReturnAppSessionEnabled() {
        return returnAppSession;
    }

    /**
     * Returns true if SystemProperty or amSession.xml property "iplanet-am-session-sfo-enabled" is true.
     *
     * Defaults to false.
     */
    public boolean isSessionFailoverEnabled() {
        return sessionFailoverEnabled;
    }

    /**
     * Returns true if amSession.xml property "iplanet-am-session-reduced-crosstalk-enabled" is true
     * (and session failover is enabled).
     *
     * @see #isSessionFailoverEnabled()
     */
    public boolean isReducedCrossTalkEnabled() {
        return sessionFailoverEnabled && reducedCrosstalkEnabled;
    }

    /**
     * Returns values of amSession.xml property "openam-session-timeout-handler-list".
     *
     * Each value should be the fully qualified name of a class implementing
     * {@link org.forgerock.openam.session.service.SessionTimeoutHandler}.
     *
     * @see org.forgerock.openam.session.service.SessionTimeoutHandler
     */
    public Set<String> getTimeoutHandlers() {
        return hotSwappableSessionServiceConfig.timeoutHandlers;
    }

    /**
     * Returns value of SystemProperty "com.sun.identity.session.application.maxCacheTime" (minutes).
     *
     * Defaults to Long.MAX_VALUE / 60 (i.e. essentially forever).
     */
    public long getApplicationMaxCachingTime() {
        return applicationMaxCachingTime;
    }

    /**
     * Returns value of amSession.xml property "iplanet-am-session-session-list-retrieval-timeout" (seconds).
     *
     * Defaults to 5.
     */
    public long getSessionRetrievalTimeout() {
        return hotSwappableSessionServiceConfig.sessionRetrievalTimeout;
    }

    /**
     * Returns value of amSession.xml property "iplanet-am-session-max-session-list-size".
     *
     * Defaults to 200.
     */
    public int getMaxSessionListSize() {
        return hotSwappableSessionServiceConfig.maxSessionListSize;
    }

    /**
     * Returns true if property change notifications are enabled for the specified property.
     *
     * Property change notifications are activated by setting the amSession.xml property
     * "iplanet-am-session-property-change-notification" to "ON" (case-insensitive); defaults to false.
     *
     * Properties for which notifications should be sent are then specified vis the amSession.xml property
     * "iplanet-am-session-notification-property-list"; no properties are selected by default.
     *
     * @param key Name of the property to check
     */
    public boolean isSendPropertyNotification(String key) {
        return hotSwappableSessionServiceConfig.isSendPropertyNotification(key);
    }

    /**
     * @return JwtSessionMapper configured according to hot-swappable SMS settings.
     */
    public JwtSessionMapper getJwtSessionMapper() {
        return hotSwappableSessionServiceConfig.jwtSessionMapperConfig.getJwtSessionMapper();
    }

    /**
     * Returns true if SystemProperty "com.iplanet.am.session.failover.useInternalRequestRouting" is enabled or
     * session failover is enabled.
     *
     * Defaults to true.
     *
     * @see #isSessionFailoverEnabled()
     */
    public boolean isUseInternalRequestRoutingEnabled() {
        return sessionFailoverEnabled && useInternalRequestRouting;
    }

    /**
     * Returns value of amSession.xml property "com.iplanet.am.session.failover.cluster.stateCheck.timeout" (milliseconds).
     *
     * Defaults to 1000.
     */
    public int getSessionFailoverClusterStateCheckTimeout() {
        return sessionFailoverClusterStateCheckTimeout;
    }

    /**
     * Returns value of amSession.xml property "com.iplanet.am.session.failover.cluster.stateCheck.period" (milliseconds).
     *
     * Defaults to 1000.
     */
    public long getSessionFailoverClusterStateCheckPeriod() {
        return sessionFailoverClusterStateCheckPeriod;
    }

    /**
     * Whether session blacklisting is enabled for stateless session logout.
     *
     * Defaults to false.
     */
    public boolean isSessionBlacklistingEnabled() {
        return hotSwappableSessionServiceConfig.sessionBlacklistEnabled;
    }

    /**
     * Maximum number of blacklisted sessions to cache in memory on each server. Beyond this number, sessions will be
     * evicted from memory (but kept in the CTS) in a least-recently used (LRU) strategy.
     *
     * Defaults to 10000.
     */
    public int getSessionBlacklistCacheSize() {
        return hotSwappableSessionServiceConfig.sessionBlacklistCacheSize;
    }

    /**
     * The interval at which to poll for changes to the session blacklist. May be 0 to indicate polling is disabled.
     *
     * @param unit the desired time unit for the poll interval.
     */
    public long getSessionBlacklistPollInterval(TimeUnit unit) {
        return unit.convert(hotSwappableSessionServiceConfig.sessionBlacklistPollIntervalSeconds, TimeUnit.SECONDS);
    }

    /**
     * Amount of time to keep sessions in the blacklist beyond their expiry time to account for clock skew.
     *
     * @param unit the desired time unit for the purge delay.
     */
    public long getSessionBlacklistPurgeDelay(TimeUnit unit) {
        return unit.convert(hotSwappableSessionServiceConfig.sessionBlacklistPurgeDelayMinutes, TimeUnit.MINUTES);
    }
}
