/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2005-2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.tomcat7;

import org.apache.catalina.Container;
import org.apache.catalina.startup.ConnectorCreateRule;
import org.apache.catalina.startup.ContextRuleSet;
import org.apache.catalina.startup.EngineRuleSet;
import org.apache.catalina.startup.HostRuleSet;
import org.apache.catalina.startup.NamingRuleSet;
import org.apache.catalina.ha.ClusterRuleSet;
import org.apache.catalina.startup.SetAllPropertiesRule;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.digester.Rule;
import org.apache.tomcat.util.digester.RuleSetBase;
import org.ow2.jonas.security.SecurityService;
import org.ow2.jonas.web.tomcat7.security.Realm;
import org.xml.sax.Attributes;

/**
 * Defines the rules to parse the Tomcat server.xml file
 * Some rules are customized for JOnAS.
 * @author Tomcat team
 * @author Florent Benoit
 */
public class JCatalinaRuleSet extends RuleSetBase {

    /**
     * Parent class loader use for Tomcat.
     */
    private ClassLoader parentClassLoader = null;

    /**
     * SecurityService for the Realms.
     */
    private SecurityService securityService = null;

    /**
     * Constructor of the rules of parsing for server.xml.
     * @param parentClassLoader the parent class loader to use
     * @param securityService SecurityService for the Realms
     */
    public JCatalinaRuleSet(final ClassLoader parentClassLoader, final SecurityService securityService) {
        super();
        this.parentClassLoader = parentClassLoader;
        this.securityService = securityService;
    }


    /**
     * Add the rules to the digester object.
     * @param digester object on whcih to define rules
     */
    public void addRuleInstances(final Digester digester) {

        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResources");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResources");


        digester.addObjectCreate("Server/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Listener");
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        digester.addSetProperties("Server/Service");
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");

        digester.addObjectCreate("Server/Service/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Listener");
        digester.addSetNext("Server/Service/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        //Executor
        digester.addObjectCreate("Server/Service/Executor",
                         "org.apache.catalina.core.StandardThreadExecutor",
                         "className");
        digester.addSetProperties("Server/Service/Executor");

        digester.addSetNext("Server/Service/Executor",
                            "addExecutor",
                            "org.apache.catalina.Executor");


        digester.addRule("Server/Service/Connector", new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[] {"executor"}));
        digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector");




        digester.addObjectCreate("Server/Service/Connector/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Connector/Listener");
        digester.addSetNext("Server/Service/Connector/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");


        // Add RuleSets for nested elements
        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
        digester.addRuleSet(new EngineRuleSet("Server/Service/"));
        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
        digester.addRuleSet(new ClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

        // When the 'engine' is found, set the parentClassLoader.
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));

        // Assign SecurityService to the declared Realms
        SetSecurityServiceRule sssr = new SetSecurityServiceRule(securityService);
        digester.addRule("Server/Service/Engine/Realm", sssr);
        digester.addRule("Server/Service/Engine/Host/Realm", sssr);
        digester.addRule("Server/Service/Engine/Host/Context/Realm", sssr);

        digester.addRuleSet(new ClusterRuleSet("Server/Service/Engine/Cluster/"));
    }
}

/**
 * Class that sets the parent class loader for the top class on the stack.
 * @author take from Catalina.
 */
final class SetParentClassLoaderRule extends Rule {

    /**
     * The parent class loader to be set.
     */
    private ClassLoader parentClassLoader = null;


    /**
     * Construct a new action.
     * @param parentClassLoader The new parent class loader
     */
    public SetParentClassLoaderRule(final ClassLoader parentClassLoader) {
        super();
        this.parentClassLoader = parentClassLoader;
    }

    /**
     * Add the requested parent class loader.
     * @param namespace the namespace URI of the matching element, or an
     *                  empty string if the parser is not namespace aware
     *                  or the element has no namespace
     * @param name the local name if the parser is namespace aware,
     *             or just the element name otherwise
     * @param attributes the attributes.
     * @throws Exception if an error occurs.
     */
    public void begin(final String namespace, final String name, final Attributes attributes) throws Exception {
        Container top = (Container) digester.peek();
        top.setParentClassLoader(parentClassLoader);
    }
}

/**
 * Class that sets the {@link SecurityService} for the top {@link Realm} on the stack.
 * @author take from Catalina.
 */
final class SetSecurityServiceRule extends Rule {

    /**
     * The {@link SecurityService} to be set.
     */
    private SecurityService securityService = null;


    /**
     * Construct a new action.
     * @param securityService The {@link SecurityService}
     */
    public SetSecurityServiceRule(final SecurityService securityService) {
        super();
        this.securityService = securityService;
    }

    /**
     * Add the {@link SecurityService}.
     * @param namespace the namespace URI of the matching element, or an
     *                  empty string if the parser is not namespace aware
     *                  or the element has no namespace
     * @param name the local name if the parser is namespace aware,
     *             or just the element name otherwise
     * @param attributes the attributes.
     * @throws Exception if an error occurs.
     */
    public void begin(final String namespace, final String name, final Attributes attributes) throws Exception {
        Object top = digester.peek();
        // We can only handle JOnAS JACC Realms
        if (top instanceof Realm) {
            Realm realm = (Realm) top;
            realm.setSecurityService(securityService);
        }

    }
}


