/*
 * 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://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.enterprise.security;

import java.util.logging.*;

import com.sun.logging.LogDomains;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.config.serverbeans.SecurityService;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.JaccProvider;
import com.sun.enterprise.config.serverbeans.ElementProperty;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.util.i18n.StringManager;

/**
 * Loads the Default Policy File into the system.
 *
 * @author Harpreet Singh
 * @author Jyri J. Virkki
 *
 */
public class PolicyLoader{

    private static Logger _logger = null;
    static {
        _logger = LogDomains.getLogger(LogDomains.SECURITY_LOGGER);
    }
    private static StringManager sm =
        StringManager.getManager("com.sun.enterprise.security");

    private static final String POLICY_PROVIDER_14 = 
        "javax.security.jacc.policy.provider";
    private static final String POLICY_PROVIDER_13 = 
        "javax.security.jacc.auth.policy.provider";
    private static final String POLICY_CONF_FACTORY = 
        "javax.security.jacc.PolicyConfigurationFactory.provider";
    private static final String POLICY_PROP_PREFIX =
        "com.sun.enterprise.jaccprovider.property.";
    private static boolean isPolicyInstalled = false;

    private static PolicyLoader _policyLoader = null;
    
    private PolicyLoader(){
    }
    /** 
     *  gets the PolicyLoader instance
     */
    public static PolicyLoader getInstance(){
        if(_policyLoader == null){
            _policyLoader = new PolicyLoader();
        }
        return _policyLoader;
    }
    /**
     * Attempts to install the policy-provider. The policy-provider
     * element in domain.xml is consulted for the class to use. Note
     * that if the javax.security.jacc.policy.provider system property
     * is set it will override the domain.xml configuration. This will
     * normally not be the case in S1AS.
     *
     * <P>The J2EE 1.3 property javax.security.jacc.auth.policy.provider is
     * checked as a last resort. It should not be set in J2EE 1.4.
     *
     */
    public void loadPolicy() {

        if (isPolicyInstalled) {
            _logger.log(Level.FINE,
                        "Policy already installed. Will not re-install.");
            return;
        }

        // get config object
        JaccProvider jacc = getConfiguredJaccProvider();

        // set config properties (see method comments)
        setPolicyConfigurationFactory(jacc);
        
        boolean j2ee13 = false;
        

        // check if system property is set
        String javaPolicy = System.getProperty(POLICY_PROVIDER_14);

        if (javaPolicy !=null) {
            // inform user domain.xml is being ignored
            _logger.log(Level.INFO, "policy.propoverride",
                        new String[] { POLICY_PROVIDER_14, javaPolicy } );
        } else {
            // otherwise obtain JACC policy-provider from domain.xml
            if (jacc != null) {
                javaPolicy = jacc.getPolicyProvider();
            }
        }
        
        if (javaPolicy == null) {
            javaPolicy = System.getProperty(POLICY_PROVIDER_13);
            if (javaPolicy != null) {
                // warn user j2ee13 property is being used
                j2ee13 = true;
                _logger.log(Level.WARNING, "policy.propoverride",
                            new String[] { POLICY_PROVIDER_13, javaPolicy} );
            }
        }

        // now install the policy provider if one was identified
        if (javaPolicy != null) {

            try {
                _logger.log(Level.INFO, "policy.loading", javaPolicy);
                
                Object obj = Class.forName(javaPolicy).newInstance();

                if (j2ee13) {
                    // Use JDK 1.3 classes if j2ee13 property being used
                    if (!(obj instanceof javax.security.auth.Policy)) {
                        String msg = 
                            sm.getString("enterprise.security.plcyload.not13");
                        throw new RuntimeException(msg);
                    }
                    javax.security.auth.Policy policy =
                        (javax.security.auth.Policy)obj;
                    javax.security.auth.Policy.setPolicy(policy);
                    policy.refresh();
                    
                } else {
                    // Otherwise use JDK 1.4 classes.
                    if (!(obj instanceof java.security.Policy)) {
                        String msg = 
                            sm.getString("enterprise.security.plcyload.not14");
                        throw new RuntimeException(msg);
                    }
                    java.security.Policy policy = (java.security.Policy)obj;
                    java.security.Policy.setPolicy(policy);
                    policy.refresh();
                }

            } catch (Exception e) {
                _logger.log(Level.SEVERE, "policy.installerror",
                            e.getMessage());
                throw new RuntimeException(e);
            }

            // Success.
            _logger.fine("Policy set to: " + javaPolicy);
            isPolicyInstalled = true;
            
        } else {
            // no value for policy provider found
            _logger.warning("policy.notloading");
        }
    }


    /**
     * Returns a JaccProvider object representing the jacc element from
     * domain.xml which is configured in security-service.
     *
     * @return The config object or null on errors.
     *
     */
    private JaccProvider getConfiguredJaccProvider() {

        JaccProvider jacc = null;

        try {
            ConfigContext configContext =
                ApplicationServer.getServerContext().getConfigContext();
            assert(configContext != null);
            SecurityService securityBean =
                ServerBeansFactory.getSecurityServiceBean(configContext);
            assert(securityBean != null);
            
            String name = securityBean.getJacc();
            jacc = securityBean.getJaccProviderByName(name);

            if (jacc == null) {
                _logger.log(Level.WARNING, "policy.nosuchname", name);
            }
                    
        } catch (Exception e) {
            _logger.warning("policy.errorreading");
            jacc =  null;
        }            

        return jacc;
    }

    
    /**
     * Set internal properties based on domain.xml configuration.
     *
     * <P>The POLICY_CONF_FACTORY property is consumed by the jacc-api
     * as documented in JACC specification. It's value is set here to the
     * value given in domain.xml <i>unless</i> it is already set in which
     * case the value is not modified.
     *
     * <P>Then and properties associated with this jacc provider from
     * domain.xml are set as internal properties prefixed with
     * POLICY_PROP_PREFIX. This is currently a workaround for bug 4846938.
     * A cleaner interface should be adopted.
     *
     */
    private void setPolicyConfigurationFactory(JaccProvider jacc) {

        if (jacc == null) {
            return;
        }
        
        // Handle JACC-specified property for factory
        String prop = System.getProperty(POLICY_CONF_FACTORY);
        if (prop != null) {
            // warn user of override
            _logger.log(Level.WARNING, "policy.factoryoverride",
                        new String[] { POLICY_CONF_FACTORY, prop } );
            
        } else {
            // use domain.xml value by setting the property to it
            String factory = jacc.getPolicyConfigurationFactoryProvider();
            if (factory == null) {
                _logger.log(Level.WARNING, "policy.nofactory");
            } else {
                System.setProperty(POLICY_CONF_FACTORY, factory);
            }
        }

        // Next, make properties of this jacc provider available to provider

        int propCount = jacc.sizeElementProperty();
        for (int i=0; i<propCount; i++) {

            ElementProperty p = jacc.getElementProperty(i);
            String name = POLICY_PROP_PREFIX + p.getName();
            String value = p.getValue();
            _logger.finest("PolicyLoader set ["+name+"] to ["+value+"]");
            System.setProperty(name, value);
        }

    }
}
