/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007 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: PolicyCache.java,v 1.2 2008/06/25 05:43:07 qcheng Exp $
 *
 * Portions Copyrighted 2011-2016 ForgeRock AS.
 */
package com.sun.identity.console.policy.model;

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

import com.iplanet.sso.SSOTokenListenersUnsupportedException;
import com.sun.identity.console.base.model.AMConsoleException;
import com.sun.identity.console.base.model.AMModelBase;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOTokenEvent;
import com.iplanet.sso.SSOTokenID;
import com.iplanet.sso.SSOTokenListener;
import com.sun.identity.shared.encode.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/* - NEED NOT LOG - */

/**
 * This class caches policy object for Console.  Token ID and a randomly
 * generated string are used as key to cache and retrieve a policy.
 */
public class PolicyCache
    implements SSOTokenListener
{        
    private static PolicyCache instance = new PolicyCache();
    private Map mapTokenIDs = new HashMap(100);

    /**
     * The generated random string is used to cache policy object when
     * we switch from on tab to another. Since it is used from caching 
     * purposes, usage of secure random is not required.
     */
    private static Random random = new Random();

    private PolicyCache() {
    }

    /**
     * Gets an instance of policy cache
     *
     * @return an instance of policy cache
     */
    public static PolicyCache getInstance() {
        return instance;
    }

    /**
     * Caches a policy object
     *
     * @param token single sign on token
     * @param policy Policy object to be cached
     * @return an unique key for retrieve this policy in future
     */
    public String cachePolicy(SSOToken token, CachedPolicy policy) {
        String randomStr = "";
        if (policy != null) {
            try {
                String key = token.getTokenID().toString();

                synchronized (mapTokenIDs) {
                    Map map = (Map) mapTokenIDs.get(key);

                    if (map == null) {
                        map = new HashMap(10);
                        token.addSSOTokenListener(this);
                    }

                    randomStr = getRandomString();
                    map.put(randomStr, policy);
                    mapTokenIDs.put(key, map);
                }
            } catch (SSOTokenListenersUnsupportedException ex) {
                // NB. If SSOTokenListenersUnsupportedException is thrown, mapTokenIDs must not
                // store reference to token ID as this will cause a memory leak.
                AMModelBase.debug.message("PolicyCache.cachePolicy: could not add sso listener: {}", ex.getMessage());
                randomStr = "";
            } catch (SSOException ssoe) {
                AMModelBase.debug.warning("PolicyCache.cachePolicy", ssoe);
                randomStr = "";
            }
        }

        return randomStr;
    }

    /**
     * Set a policy object with a given ID.
     *
     * @param token Single sign on token.
     * @param cachedID ID of cached policy.
     * @param policy Policy object to be cached.
     */
    public void setPolicy(SSOToken token, String cachedID, CachedPolicy policy){
        if (policy != null) {
            try {
                String key = token.getTokenID().toString();

                synchronized(mapTokenIDs) {
                    Map map = (Map)mapTokenIDs.get(key);

                    if (map == null) {
                        map = new HashMap(10);
                        token.addSSOTokenListener(this);
                    }

                    map.put(cachedID, policy);
                    mapTokenIDs.put(key, map);
                }
            } catch (SSOException ssoe) {
                // NB. If SSOTokenListenersUnsupportedException is thrown, mapTokenIDs must not
                // store reference to token ID as this will cause a memory leak.
                AMModelBase.debug.warning("PolicyCache.replacePolicy", ssoe);
            }
        }
    }

    /**
     * Returns cached policy object
     *
     * @param token single sign on token
     * @param cacheID Key for retrieve this policy
     * @return policy Policy object.
     * @throws AMConsoleException if policy object cannot be located.
     */
    public CachedPolicy getPolicy(SSOToken token, String cacheID)
        throws AMConsoleException
    {
        CachedPolicy policy = null;
        String key = token.getTokenID().toString();
        Map map = (Map) mapTokenIDs.get(key);

        if (map != null) {
            policy = (CachedPolicy) map.get(cacheID);
        }

        if (policy == null) {
            throw new 
                AMConsoleException("Cannot locate cached policy " + cacheID);
        }

        return policy;
    }

    /**
     * Gets notification when single sign on token changes state.
     *
     * @param evt single sign on token event
     */
    public void ssoTokenChanged(SSOTokenEvent evt) {
        try {
            int type = evt.getType();

            switch (type) {
            case SSOTokenEvent.SSO_TOKEN_IDLE_TIMEOUT:
            case SSOTokenEvent.SSO_TOKEN_MAX_TIMEOUT:
            case SSOTokenEvent.SSO_TOKEN_DESTROY:
                SSOToken token = evt.getToken();
                clearAllPolicies(token.getTokenID());
                break;
            }
        } catch (SSOException ssoe) {
            AMModelBase.debug.warning("PolicyCache.ssoTokenChanged", ssoe);
        }
    }

    /**
     * Clears all cached policy of a given single sign on token ID
     *
     * @param tokenID single sign on token ID
     */
    protected void clearAllPolicies(SSOTokenID tokenID) {
        boolean removed = false;
        String key = tokenID.toString();

        synchronized(mapTokenIDs) {
            removed = (mapTokenIDs.remove(key) != null);
        }

        if (removed && AMModelBase.debug.messageEnabled()) {
            AMModelBase.debug.warning("PolicyCache.clearAllPolicies," + key);
        }
    }

    /**
     * Gets a random string
     *
     * @return random string
     */
    private static String getRandomString() {
        StringBuilder sb = new StringBuilder(30);
        byte[] keyRandom = new byte[5];
        random.nextBytes(keyRandom);
        sb.append(currentTimeMillis());
        sb.append(Base64.encode(keyRandom));
        return (sb.toString());
    }
}
