/*
 * File: PolicyManager.java
 *
 * Copyright 2007 Macquarie E-Learning Centre Of Excellence
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.fcrepo.server.security.xacml.pdp.finder.policy;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.fcrepo.server.security.xacml.pdp.data.PolicyIndex;
import org.fcrepo.server.security.xacml.pdp.data.PolicyIndexException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.jboss.security.xacml.sunxacml.AbstractPolicy;
import org.jboss.security.xacml.sunxacml.EvaluationCtx;
import org.jboss.security.xacml.sunxacml.MatchResult;
import org.jboss.security.xacml.sunxacml.PolicyMetaData;
import org.jboss.security.xacml.sunxacml.PolicySet;
import org.jboss.security.xacml.sunxacml.Target;
import org.jboss.security.xacml.sunxacml.TargetMatch;
import org.jboss.security.xacml.sunxacml.TargetSection;
import org.jboss.security.xacml.sunxacml.combine.PolicyCombiningAlgorithm;
import org.jboss.security.xacml.sunxacml.ctx.Status;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinder;

/**
 * This class interacts with the policy cache on behalf of the PolicyFinder
 * modules. It also does the matching of the policies and creation of policy
 * sets.
 *
 * @author nishen@melcoe.mq.edu.au
 */
public class PolicyManager {

    private static final Logger logger =
            LoggerFactory.getLogger(PolicyManager.class);

    private final PolicyIndex m_policyIndex;

    private PolicyCombiningAlgorithm m_combiningAlg = null;
    
    private final PolicyFinder m_policyFinder;

    private Target m_target = null;

    //private PolicyReader m_policyReader = null;

    // the policy identifier for any policy sets we dynamically create
    private static final String PARENT_POLICY_ID =
            "urn:com:sun:xacml:support:finder:dynamic-policy-set";

    private static URI parentPolicyId = null;
    static {
        try{
            parentPolicyId = new URI(PARENT_POLICY_ID);
        } catch (Exception e) {}
    }

    /**
     * This constructor creates a PolicyManager instance. It takes a
     * PolicyFinder its argument. Its purpose is to obtain a set of policies
     * from the Policy Index , match them against the evaluation context and
     * return a policy or policy set that conforms to the evaluation context.
     *
     * @param policyIndex
     *        the policy index
     * @param combiningAlg
     *        the policy combining algorithm
     * @param policyFinder
     *        the policy finder
     */
    public PolicyManager(PolicyIndex policyIndex, PolicyCombiningAlgorithm combiningAlg,
                         PolicyFinder policyFinder) {

        m_policyIndex = policyIndex;
        m_combiningAlg = combiningAlg;
        m_policyFinder = policyFinder;
        m_target =
                new Target(new TargetSection(null,
                                             TargetMatch.SUBJECT,
                                             PolicyMetaData.XACML_VERSION_2_0),
                           new TargetSection(null,
                                             TargetMatch.RESOURCE,
                                             PolicyMetaData.XACML_VERSION_2_0),
                           new TargetSection(null,
                                             TargetMatch.ACTION,
                                             PolicyMetaData.XACML_VERSION_2_0),
                           new TargetSection(null,
                                             TargetMatch.ENVIRONMENT,
                                             PolicyMetaData.XACML_VERSION_2_0));
    }
    
    /**
     * Obtains a policy or policy set of matching policies from the policy
     * store. If more than one policy is returned it creates a dynamic policy
     * set that contains all the applicable policies.
     *
     * @param eval
     *        the Evaluation Context
     * @return the Policy/PolicySet that applies to this EvaluationCtx
     * @throws TopLevelPolicyException
     * @throws PolicyIndexException
     */
    public AbstractPolicy getPolicy(EvaluationCtx eval)
            throws TopLevelPolicyException, PolicyIndexException {
        Map<String, AbstractPolicy> potentialPolicies =
                m_policyIndex.getPolicies(eval, m_policyFinder);
        logger.debug("Obtained policies: {}", potentialPolicies.size());

        AbstractPolicy policy = matchPolicies(eval, potentialPolicies);
        logger.debug("Matched policies and created abstract policy.");

        return policy;
    }

    /**
     * Given and Evaluation Context and a list of potential policies, this
     * method matches each policy against the Evaluation Context and extracts
     * only the ones that match. If there is more than one policy, a new dynamic
     * policy set is created and returned. Otherwise the policy that is found is
     * returned.
     *
     * @param eval
     *        the Evaluation Context
     * @param policyList
     *        the list of policies as a map with PolicyId as key and policy as a
     *        byte array as the value
     * @return the Policy/PolicySet that applies to this EvaluationCtx
     * @throws {@link TopLevelPolicyException}
     */
    private AbstractPolicy matchPolicies(EvaluationCtx eval,
                                         Map<String, AbstractPolicy> policyList)
            throws TopLevelPolicyException {
        // setup a list of matching policies
        Map<String, AbstractPolicy> list =
                new HashMap<String, AbstractPolicy>();

        // get an iterator over all the identifiers
        for (String policyId : policyList.keySet()) {
            AbstractPolicy policy = policyList.get(policyId);

            MatchResult match = policy.match(eval);

            int result = match.getResult();
            if (result == MatchResult.INDETERMINATE) {
                throw new TopLevelPolicyException(match.getStatus());
            }

            // if we matched, we keep track of the matching policy...
            if (result == MatchResult.MATCH) {
                // ...first checking if this is the first match and if
                // we automatically nest policies
                if (m_combiningAlg == null && list.size() > 0) {
                    ArrayList<String> code = new ArrayList<String>();
                    code.add(Status.STATUS_PROCESSING_ERROR);
                    Status status =
                            new Status(code, "too many applicable"
                                    + " top-level policies");
                    throw new TopLevelPolicyException(status);
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Matched policy: {}", policyId);
                }

                list.put(policyId, policy);
            }
        }

        // no errors happened during the search, so now take the right
        // action based on how many policies we found
        switch (list.size()) {
            case 0:
                return null;
            case 1:
                Iterator<AbstractPolicy> i = list.values().iterator();
                AbstractPolicy p = i.next();
                return p;
            default:
                return new PolicySet(parentPolicyId,
                                     m_combiningAlg,
                                     m_target,
                                     new ArrayList<AbstractPolicy>(list
                                             .values()));
        }
    }
}