/*
 * 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.
 */

/*
 * AuditManager.java
 *
 * Created on July 28, 2003, 1:56 PM
 */

package com.sun.enterprise.security.audit;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import com.sun.appserv.security.AuditModule;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.SecurityService;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.ElementProperty;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.logging.LogDomains;
import com.sun.enterprise.util.LocalStringManagerImpl;

/**
 *
 * @author  Harpreet Singh
 * @author  Shing Wai Chan
 */
public final class AuditManager {
    static final String NAME = "name";
    static final String CLASSNAME = "classname";

    private static final String AUDIT_MGR_WS_INVOCATION_KEY = 
        "auditmgr.webServiceInvocation";
    private static final String AUDIT_MGR_EJB_AS_WS_INVOCATION_KEY = 
        "auditmgr.ejbAsWebServiceInvocation";
    private static final String AUDIT_MGR_SERVER_STARTUP_KEY = 
        "auditmgr.serverStartup";
    private static final String AUDIT_MGR_SERVER_SHUTDOWN_KEY = 
        "auditmgr.serverShutdown";

    private static final Logger _logger = 
             LogDomains.getLogger(LogDomains.SECURITY_LOGGER);

    private static final LocalStringManagerImpl _localStrings =
	new LocalStringManagerImpl(AuditManager.class);

    private List instances = Collections.synchronizedList(new ArrayList());
    // just a copy of names of the audit classes - helpful for log messages
    // since we will not have a lot of audit classes, keeping a duplicate copy
    // seems reasonable.
    private Map moduleToNameMap = new HashMap();
    private Map nameToModuleMap = new HashMap();
    // make this accessible to the containers so that the cost of non-audit case, 
    // is just a comparision.
    private static boolean auditOn = false;
    /** Creates a new instance of AuditManager */
    AuditManager() {
    }
    
    /**
     * This method initializes AuditManager which load audit modules and
     * audit enabled flag
     */
    public void loadAuditModules() {
        try {
            ConfigContext configContext =
                ApplicationServer.getServerContext().getConfigContext();
            assert(configContext != null);

            Server configBean = ServerBeansFactory.getServerBean(configContext);
            assert(configBean != null);

            SecurityService securityBean =
                ServerBeansFactory.getSecurityServiceBean(configContext);
            assert(securityBean != null);
            // @todo will be removed to incorporate the new structure.
            boolean auditFlag = securityBean.isAuditEnabled();

            setAuditOn(auditFlag);
            com.sun.enterprise.config.serverbeans.AuditModule[] am =
                    securityBean.getAuditModule();

            for (int i = 0; i < am.length; i++){
                try {
                    String name = am[i].getName();
                    String classname = am[i].getClassname();
                    Properties p = new Properties();
                    //XXX should we remove this two extra properties
                    p.setProperty(NAME, name);
                    p.setProperty(CLASSNAME, classname);
                    ElementProperty[] ep = am[i].getElementProperty();
                    int epsize = am[i].sizeElementProperty();
                    for (int j = 0; j < epsize; j++){
                        String nme = ep[j].getName();
                        String val = ep[j].getValue();
                        p.setProperty(nme, val);
                    }
                    AuditModule auditModule = loadAuditModule(classname, p);
                    instances.add(auditModule);
                    moduleToNameMap.put(auditModule, name);
                    nameToModuleMap.put(name, auditModule);
                } catch(Exception ex){
                     String msg = _localStrings.getLocalString(
                         "auditmgr.loaderror", 
                         "Audit: Cannot load AuditModule = {0}",
                         new Object[]{ am[i].getName() });
                     _logger.log(Level.WARNING, msg, ex);                    
                }
            }
        } catch (Exception e) {
            String msg = _localStrings.getLocalString("auditmgr.badinit", 
                   "Audit: Cannot load Audit Module Initialization information. AuditModules will not be loaded.");
            _logger.log(Level.WARNING, msg, e);
        }
    }

    /**
     * Add the given audit module to the list of loaded audit module.
     * Adding the same name twice will override previous one.
     * @param name of auditModule
     * @param am an instance of a class extending AuditModule that has been 
     * successfully loaded into the system.
     * @exception 
     */
    void addAuditModule(String name, String classname, Properties props)
            throws Exception {
        // make sure only a name corresponding to only one auditModule
        removeAuditModule(name);
        AuditModule am = loadAuditModule(classname, props);

        moduleToNameMap.put(am, name);
        nameToModuleMap.put(name, am);
        // clone list to resolve multi-thread issues in looping instances
        List list = new ArrayList();
        Collections.copy(instances, list);
        list.add(am);        
        instances = Collections.synchronizedList(list);
    }

    /**
     * Remove the audit module of given name from the loaded list.
     * @param name of auditModule
     */
    void removeAuditModule(String name) {
        Object am = nameToModuleMap.get(name);
        if (am != null) {
            nameToModuleMap.remove(name);
            moduleToNameMap.remove(am);
            // clone list to resolve multi-thread issues in looping instances
            List list = new ArrayList();
            Collections.copy(instances, list);
            list.remove(am);        
            instances = Collections.synchronizedList(list);
        }
    }

    /**
     * Get the audit module of given name from the loaded list.
     * @param name of auditModule
     */
    AuditModule getAuditModule(String name) {
        return (AuditModule)nameToModuleMap.get(name);
    }


    /**
     * This method return auditModule with given classname and properties.
     * @param classname
     * @param props
     * @exception
     */
    private AuditModule loadAuditModule(String classname,
            Properties props) throws Exception {
        AuditModule auditModule = null;
        Class am = Class.forName(classname);
        Object obj =  am.newInstance();
        auditModule = (AuditModule) obj;
        auditModule.init(props);
        return auditModule;
    }
    
    /**
     * logs the authentication call for all the loaded modules.
     * @see com.sun.appserv.security.AuditModule.authentication
     */
    public void authentication(String user, String realm, boolean success){
        if(auditOn){
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = null;
                try{
                    am = (AuditModule)list.get(i);
                    am.authentication(user, realm, success);
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString("auditmgr.authentication",
                    " Audit Module {0} threw the followin exception during authentication:", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }
    /**
     * logs the web authorization call for all loaded modules
     * @see com.sun.appserv.security.AuditModule.webInvocation
     */
    public void webInvocation(String user, HttpServletRequest req, 
        String type, boolean success){
        if(auditOn){
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.webInvocation(user, req, type, success);
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString("auditmgr.webinvocation",
                    " Audit Module {0} threw the followin exception during web invocation :", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }
    /**
     * logs the ejb authorization call for all ejb modules
     * @see com.sun.appserv.security.AuditModule.ejbInvocation
     */
    public void ejbInvocation(String user, String ejb, String method, 
            boolean success){
        if(auditOn){
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.ejbInvocation(user, ejb, method, success);
                } catch (Exception e){
                        String name = (String)moduleToNameMap.get(am);
                        String msg = 
                        _localStrings.getLocalString("auditmgr.ejbinvocation",
                        " Audit Module {0} threw the followin exception during ejb invocation :", 
                            new Object[] {name});
                        _logger.log(Level.INFO, msg, e);
                }

            }
        }
    }    
    
    /**
     * This method is called for the web service calls with MLS set 
     * and the endpoints deployed as servlets  
     * @see com.sun.appserv.security.AuditModule.webServiceInvocation
     */
    public void webServiceInvocation(String uri, String endpoint, 
                                     boolean validRequest){
        if(auditOn){
            // This surely is not the most optimal way of iterating through
            // the list of audit modules since I think the list is static
            // For now just do as its done for ejb/web audits - TODO later
            // Another thing to do would be make the list of audit modules
            // generic, preventing type casting at runtime 
            // like: List<AuditModule> list
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.webServiceInvocation(uri, endpoint,  validRequest);
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString(AUDIT_MGR_WS_INVOCATION_KEY,
                    " Audit Module {0} threw the following exception during "+
                    "web service invocation :", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }


    /**
     * This method is called for the web service calls with MLS set 
     * and the endpoints deployed as servlets  
     * @see com.sun.appserv.security.AuditModule.webServiceInvocation
     */
    public void ejbAsWebServiceInvocation(String endpoint, boolean validRequest){
        if(auditOn){

            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.ejbAsWebServiceInvocation(endpoint, validRequest);
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString(AUDIT_MGR_EJB_AS_WS_INVOCATION_KEY,
                    " Audit Module {0} threw the following exception during "+
                    "ejb as web service invocation :", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }

    public void serverStarted(){
        if(auditOn){
            // This surely is not the most optimal way of iterating through
            // the list of audit modules since I think the list is static
            // For now just do as its done for ejb/web audits - TODO later
            // Another thing to do would be make the list of audit modules
            // generic, preventing type casting at runtime 
            // like: List<AuditModule> list
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.serverStarted();
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString(AUDIT_MGR_SERVER_STARTUP_KEY,
                    " Audit Module {0} threw the following exception during "+
                    "server startup :", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }

    public void serverShutdown(){
        if(auditOn){
            // This surely is not the most optimal way of iterating through
            // the list of audit modules since I think the list is static
            // For now just do as its done for ejb/web audits - TODO later
            // Another thing to do would be make the list of audit modules
            // generic, preventing type casting at runtime 
            // like: List<AuditModule> list
            List list = instances;
            int size = list.size();
            for (int i = 0; i < size; i++) {
                AuditModule am = (AuditModule)list.get(i);
                try{
                    am.serverShutdown();
                } catch (Exception e){
                    String name = (String)moduleToNameMap.get(am);
                    String msg = 
                    _localStrings.getLocalString(AUDIT_MGR_SERVER_SHUTDOWN_KEY,
                    " Audit Module {0} threw the following exception during "+
                    "server shutdown :", 
                        new Object[] {name});
                    _logger.log(Level.INFO, msg, e);
                }
            }
        }
    }

    void setAuditOn(boolean auditOn) {
        this.auditOn = auditOn;
    }
    
    public boolean isAuditOn() {
        return auditOn;
    }
    
}
