/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 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.tomcat6;

import static org.ow2.jonas.ws.jaxws.IJAXWSService.KEY_WEB_SERVICES_METADATAS;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.NamingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.util.DefaultAnnotationProcessor;
import org.apache.tomcat.util.digester.Digester;
import org.objectweb.util.monolog.api.BasicLevel;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.ow2.easybeans.deployment.api.EZBInjectionHolder;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.jndi.checker.api.IResourceCheckerManager;
import org.ow2.jonas.lib.bootstrap.LoaderManager;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.security.SecurityService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.tm.TransactionService;
import org.ow2.jonas.web.JWebContainerService;
import org.ow2.jonas.web.JWebContainerServiceException;
import org.ow2.jonas.web.base.BaseWebContainerService;
import org.ow2.jonas.web.base.lib.PermissionManager;
import org.ow2.jonas.web.tomcat6.security.Realm;
import org.ow2.jonas.web.tomcat6.tx.TransactionValve;
import org.ow2.jonas.web.tomcat6.versioning.ContextFinder;
import org.ow2.jonas.web.tomcat6.ws.WSContextLifecycleListener;
import org.ow2.jonas.web.tomcat6.ws.WSDeployment;
import org.ow2.jonas.web.tomcat6.ws.WebservicesWebDeployer;
import org.ow2.jonas.ws.jaxws.IWebServiceDeploymentManager;
import org.ow2.jonas.ws.jaxws.ejb.IWebDeployer;
import org.ow2.util.ee.metadata.war.api.IWarClassMetadata;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.url.URLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Implementation of the web container service for Tomcat 6.
 * This service also adds virtual path support to Tomcat 6.
 *
 * @author Florent BENOIT
 * @author S. Ali Tokmen
 */
public class Tomcat6Service extends BaseWebContainerService implements JWebContainerService {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(Tomcat6Service.class);

    /**
     * Relative path of the configuration file.
     */
    protected static final String CONFIG_FILE = "conf" + File.separator + "tomcat6-server.xml";

    /**
     * The reference on the catalina server.
     */
    private Server server = null;

    /**
     * External class loader.
     */
    private ClassLoader externalLoader = null;

    /**
     * Web container started ?
     */
    private boolean tomcatStarted = false;

    /**
     * Security service.
     */
    private SecurityService securityService = null;

    /**
     * Tomcat's BundleContext.
     */
    private BundleContext context;

    /**
     * {@link IWebServiceDeploymentManager} registration key.
     */
    private ServiceRegistration reg;

    /**
     * {@link IWebDeployer} registration key.
     */
    private ServiceRegistration reg2;

    /**
      * Transaction service.
      */
     private TransactionService transactionService = null;

    /**
     * Link to the resource checker manager.
     */
    private IResourceCheckerManager resourceCheckerManager = null;

    /**
     * Default constructor.
     */
    public Tomcat6Service(final BundleContext context) {
        super();
        this.context = context;
    }

    /**
     * Init the environment of catalina set catalina.home, catalina.base and
     * unset the tomcat naming.
     * @throws ServiceException if catalina home is not set
     */
    protected void initCatalinaEnvironment() throws ServiceException {

        // Assume that CATALINA_HOME = JONAS_ROOT and CATALINA_BASE = JONAS_BASE
        String jonasRoot = System.getProperty("jonas.root");
        String jonasBase = System.getProperty("jonas.base");

        String catalinaHome = System.getProperty("catalina.home");
        String catalinaBase = System.getProperty("catalina.base");

        // check that this value is ok ?
        if (catalinaHome != null && !jonasRoot.equals(catalinaHome)) {
            logger.warn("The CATALINA_HOME property was set to ''{0}'', fix it by using JONAS_ROOT ''{1}''", catalinaHome,
                    jonasRoot);
        }
        // override
        System.setProperty("catalina.home", jonasRoot);

        // CATALINA_BASE = JONAS_BASE ?
        if (catalinaBase != null && !jonasBase.equals(catalinaBase)) {
            logger.warn("The CATALINA_BASE property was set to ''{0}'', fix it by using JONAS_BASE ''{1}''", catalinaBase,
                    jonasBase);
        }
        // override
        System.setProperty("catalina.base", jonasBase);

        // use the JOnAS naming instead of the Tomcat naming
        System.setProperty("catalina.useNaming", "false");
    }

    /**
     * Start the Catalina service in a new thread.
     * @throws ServiceException if the startup failed.
     */
    @Override
    public void doStart() throws ServiceException {

        initCatalinaEnvironment();

        // On Demand feature not enabled, start the web container now
        if (!isOnDemandFeatureEnabled()) {
            startInternalWebContainer();
        } // else delay this launch

        // ... and run super method
        super.doStart();

    }


    /**
     * Starts the specific code for the web container implementation.
     * This allows to start the internal container on demand (if there is an access on the http proxy port for example)
     * @throws JWebContainerServiceException if container is not started
     */
    @Override
    public synchronized void startInternalWebContainer() throws JWebContainerServiceException {
        // Already started
        if (tomcatStarted) {
            return;
        }

        // parent ClassLoader
        LoaderManager lm = LoaderManager.getInstance();
        try {
            externalLoader = lm.getExternalLoader();
        } catch (Exception e1) {
            throw new ServiceException("Cannot get Application/Tomcat ClassLoader", e1);
        }

        // Initialize the context finder for the optional versioning service
        // Context finder will not do anything if the versioning service is not
        // present (can be removed or added on-the-fly).
        ContextFinder.setParent(this);
        ContextFinder.setJmxService(jmxService);

        // Create the digester for the parsing of the server.xml.
        Digester digester = createServerDigester();

        // Execute the digester for the parsing of the server.xml.
        // And configure the catalina server.
        File configFile = null;

        try {
            configFile = getConfigFile();
        } catch (FileNotFoundException e) {
            logger.error("Cannot find the file ''{0}''", CONFIG_FILE, e);
            throw new ServiceException("Cannot find the configuration file", e);
        }

        try {
            InputSource is = new InputSource("file://" + configFile.getAbsolutePath());
            FileInputStream fis = new FileInputStream(configFile);
            is.setByteStream(fis);
            digester.setClassLoader(this.getClass().getClassLoader());
            digester.push(this);
            digester.parse(is);
            fis.close();
        } catch (Exception e) {
            logger.error("Cannot parse the configuration file ''{0}''", configFile, e);
            throw new ServiceException("Cannot parse the configuration file '" + configFile + "'", e);
        }

        // Set the Domain and the name for each known Engine
        for (StandardEngine engine : getEngines()) {
            // WARNING : the order of th two next lines is very important.
            // The domain must be set in first and the name after.
            // In the others cases, Tomcat 6 doesn't set correctly these two
            // properties
            // because there are somes controls that forbid to have a difference
            // between
            // the name and the domain. Certainly a bug !
            engine.setDomain(getDomainName());
            engine.setName(getDomainName());
        }

        // If OnDemand Feature is enabled, the http connector port needs to be changed
        // And keep-alive feature should be turned-off (to monitor all requests)
        if (isOnDemandFeatureEnabled()) {
            Service[] services = getServer().findServices();

            // set name of the first service
            if (services.length > 0) {
                services[0].setName(getDomainName());
            }

            // Get connector of each service
            for (int s = 0; s < services.length; s++) {
                Connector[] connectors = services[s].findConnectors();
                if (connectors.length >= 1) {
                    // Only for the first connector
                    Connector connector = connectors[0];
                    connector.setProperty("maxKeepAliveRequests", "1");
                    connector.setPort(getOnDemandRedirectPort());
                }
            }
        }


        // Start Tomcat server in an execution block
        IExecution<Void> startExec = new IExecution<Void>() {
            public Void execute() throws ServiceException {
                // Finaly start catalina ...
                if (server instanceof Lifecycle) {
                    try {
                        Field domainField = server.getClass().getDeclaredField("domain");
                        domainField.setAccessible(true);
                        domainField.set(server, getDomainName());
                        server.initialize();
                        domainField.set(server, getDomainName());
                        Service[] services = getServer().findServices();
                        // set name of the first service
                        if (services.length > 0) {
                            services[0].setName(getDomainName());
                        }
                        ((Lifecycle) server).start();
                    } catch (Exception e) {
                        logger.error("Cannot start the Tomcat server", e);
                        throw new ServiceException("Cannot start the Tomcat server", e);
                    }
                }

                return null;
            }
        };

        // Execute
        ExecutionResult<Void> startExecResult = RunnableHelper.execute(getClass().getClassLoader(), startExec);

        // Throw an ServiceException if needed
        if (startExecResult.hasException()) {
            logger.error("Cannot start the Tomcat server", startExecResult.getException());
            throw new ServiceException("Cannot start the Tomcat Server", startExecResult.getException());
        }

        // Here, register the IWSDM
        WSDeployment manager = new WSDeployment(this);
        manager.setWorkDirectory(WORK_DIR);
        reg = context.registerService(IWebServiceDeploymentManager.class.getName(),
                                      manager,
                                      null);

        // Here, register the IWebDeployer
        WebservicesWebDeployer webDeployer = new WebservicesWebDeployer(this);
        webDeployer.setWorkDirectory(WORK_DIR);
        reg2 = context.registerService(IWebDeployer.class.getName(),
                                       webDeployer,
                                       null);

        // Tomcat is started
        tomcatStarted = true;
    }


    /**
     * Set the environment of the web container inside the given context.
     * @param ctxParam the java:comp/env/ environment where is stored the values of the web container environment.
     * @param ezbInjectionHolder the easybeans injection handler.
     * @param webAppClassLoader the classloader of the web application.
     * @throws JWebContainerServiceException if the populating of the environment failed.
     */
    @Override
    protected void setWebEnvironment(final javax.naming.Context ctxParam, final EZBInjectionHolder ezbInjectionHolder,
            final ClassLoader webAppClassLoader) throws JWebContainerServiceException {

        super.setWebEnvironment(ctxParam, ezbInjectionHolder, webAppClassLoader);

        // Store bundle context
        try {
            getNaming().getComponentContext(webAppClassLoader).bind("comp/env/BundleContext", context);
        } catch (NamingException e) {
            throw new JWebContainerServiceException("Cannot bind bundle context", e);
        }
    }

    /**
     * Checks if the internal web container has been started.
     * @return true if it is already started
     */
    @Override
    public boolean isInternalContainerStarted() {
        return tomcatStarted;
    }



    /**
     * Stop the Catalina service.
     * @throws ServiceException if the stop failed.
     */
    @Override
    protected void doStop() throws ServiceException {
        // Undeploy the wars ...
        super.doStop();

        // Unregister the WSDM
        if (reg != null) {
            reg.unregister();
        }

        if (reg2 != null) {
            reg2.unregister();
        }


        // ... and shut down embedded catalina
        if (tomcatStarted && server instanceof Lifecycle) {
            try {
                ((Lifecycle) server).stop();
            } catch (Exception e) {
                throw new ServiceException(e.getMessage(), e);
            }
        }
        tomcatStarted = false;
    }

    /**
     * Deploy a specific WAR file specified in the context.
     * @param ctx the context which contains the configuration in order to
     *        deploy the WAR.
     * @throws JWebContainerServiceException if the registration of the WAR
     *         failed.
     */
    @Override
    protected void doRegisterWar(final javax.naming.Context ctx) throws JWebContainerServiceException {
        // Get the 7 parameters :
        // - warURL is the URL of the war to register (required param).
        // - contextRoot is the context root to which this application
        // should be installed (must be unique) (required param).
        // - hostName is the name of the host on which deploy the war
        // (optional param taken into account only if no <context> element
        // was declared in server.xml for this web application) .
        // - Name of the Ear application of this war if any
        // - java2DelegationModel the compliance to java2 delegation model
        // - parentCL the war classloader of this war.
        // - jonasDD JOnAS Deployment Desc content
        // - permissionManager JACC permission manager

        URL warURL = null;
        URL earURL = null;
        URL unpackedWarURL = null;
        String contextRoot = null;
        boolean java2DelegationModel = true;
        PermissionManager permissionManager = null;
        boolean inEarCase = true;
        String earAppName = null;
        WebContainerDeploymentDesc webDD = null;
        String userURI = null;
        Map<String, IWarClassMetadata> webservices = null;

        try {
            warURL = (URL) ctx.lookup("warURL");
            unpackedWarURL = (URL) ctx.lookup("unpackedWarURL");
            contextRoot = (String) ctx.lookup("contextRoot");
            Boolean bool = (Boolean) ctx.lookup("java2DelegationModel");
            java2DelegationModel = bool.booleanValue();
            webDD = (WebContainerDeploymentDesc) ctx.lookup("webDD");
            permissionManager = (PermissionManager) ctx.lookup("permissionManager");
            userURI = (String) ctx.lookup("userURI");
            webservices = (Map<String, IWarClassMetadata>) ctx.lookup(KEY_WEB_SERVICES_METADATAS);
        } catch (NamingException e) {
            logger.error("Unable to get default parameters", e);
            throw new JWebContainerServiceException("Unable to get default parameters", e);
        }

        // Get injection holder
        EZBInjectionHolder  injectionHolder = null;
        try {
            injectionHolder = (EZBInjectionHolder) ctx.lookup(EZBInjectionHolder.class.getName());
        } catch (NamingException e) {
            logger.debug("No injection holder");
        }

        try {
            earAppName = (String) ctx.lookup("earAppName");
            earURL = (URL) ctx.lookup("earURL");
        } catch (NamingException e) {
            // no ear case, so no ear application name
            inEarCase = false;
            // set earURL to something only to avoid null value
            earURL = warURL;
        }

        ClassLoader webClassLoader = null;

        try {
            webClassLoader = (ClassLoader) ctx.lookup("parentCL");
        } catch (NamingException e) {
            logger.error("Unable to get parentCL parameter", e);
            throw new JWebContainerServiceException("Unable to get parentCL parameter", e);
        }

        String hostName = null;
        try {
            hostName = (String) ctx.lookup("hostName");
        } catch (NamingException e) {
            hostName = "";
        }

        // Install a new web application, whose web application archive is
        // at the specified URL, into this container with the specified
        // context root.
        // A context root of "" (the empty string) should be used for the root
        // application for this container. Otherwise, the context root must
        // start with a slash.

        if (contextRoot.equals("/")) {
            contextRoot = "";
        } else {
            contextRoot = "/" + contextRoot;
        }

        // Install the war.
        File fWar = URLUtils.urlToFile(warURL);

        // Unpacked path of the war
        File destDir = null;
        if (fWar.isDirectory()) {
            destDir = URLUtils.urlToFile(warURL);
        } else {
            destDir = URLUtils.urlToFile(unpackedWarURL);
        }

        // META-INF/context.xml file
        File contextXmlFile = new File(destDir, File.separator + "META-INF" + File.separator + "context.xml");

        // Check if some contexts were configured in server.xml
        List<JOnASStandardContext> jonasContexts = getConfiguredMatchingJonasContexts(contextRoot, fWar, destDir.getPath());

        Host deployer = null;
        // The context was not found in server.xml, a new context will be
        // created.
        if (jonasContexts.isEmpty()) {
            // Find host on which deploy the context
            deployer = findHost(hostName);

            // Create context (false because not in server.xml)
            JOnASStandardContext context = new JOnASStandardContext(false, java2DelegationModel, inEarCase);
            context.setDocBase(destDir.getPath());
            context.setPath(contextRoot);

            // Use the JOnAS context config
            ContextConfig config = createContextConfig();

            // add the config
            ((Lifecycle) context).addLifecycleListener(config);
            jonasContexts.add(context);
        }

        // Configure these contexts and start them
        for (JOnASStandardContext jStdCtx : jonasContexts) {

            // FIXME Only needed once (static reference)
            jStdCtx.setTomcatService(this);

            // Set the parent class loader
            jStdCtx.setParentClassLoader(webClassLoader);

            // Delegation model
            jStdCtx.setDelegate(java2DelegationModel);
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "Webapp class loader java 2 delegation model set to " + java2DelegationModel);
            }


            // Add valves
            // for setting unauthenticated user
            jStdCtx.addValve(new ResetAuthenticationValve());
            // For checking transactions
            if (transactionService != null) {
                jStdCtx.addValve(new TransactionValve(transactionService.getTransactionManager()));
            }
            // For checking resources
            if (resourceCheckerManager != null) {
                jStdCtx.addValve(new CheckOpenResourcesValve(resourceCheckerManager));
            }



            // Add the WS related ContextListener only if there are some webservices in the webapp
            if (!webservices.isEmpty() && (getJAXWSService() != null)) {
                WSContextLifecycleListener listener = new WSContextLifecycleListener(webservices,
                                                                                     getJAXWSService());
                jStdCtx.addLifecycleListener(listener);
            }

            // META-INF/context.xml file support
            if (contextXmlFile.exists()) {
                jStdCtx.setConfigFile(contextXmlFile.getAbsolutePath());
            }

            // The Annotation processor object needs to be initialized with the
            // component context. Else, the processor does nothing.
            javax.naming.Context envCtx = null;
            try {
                envCtx = (javax.naming.Context) getNaming().getComponentContext(webClassLoader).lookup("comp/env");
            } catch (NamingException e) {
                 getLogger().log(BasicLevel.ERROR, "Cannot get the context of the webapplication '" + warURL + "'." , e);
            }
            DefaultAnnotationProcessor annotationProcessor = new DefaultAnnotationProcessor(envCtx);
            annotationProcessor.setEncBindingHolder(webDD.getENCBindingHolder());
            jStdCtx.setAnnotationProcessor(annotationProcessor);

            // JSR 77
            jStdCtx.setJ2EEServer(getJonasServerName());
            jStdCtx.setServer(J2eeObjectName.J2EEServer(getDomainName(), getJonasServerName()).toString());
            // Get info about JavaVM in standard J2EEServer MBean
            MBeanServer mbeanServer = jmxService.getJmxServer();
            ObjectName j2eeServerOn = J2eeObjectName.J2EEServer(getDomainName(), getJonasServerName());
            try {
                String[] as = (String[]) mbeanServer.getAttribute(j2eeServerOn, "javaVMs");
                jStdCtx.setJavaVMs(as);
            } catch (Exception e) {
                // String err =
                // i18n.getMessage("CatJWebServ.doRegisterWar.startContextError",
                // jStdCtx);
                // getLogger().log(BasicLevel.WARN, err + e.getMessage());
                getLogger().log(BasicLevel.WARN, "Set MBean JVM error : " + e.getClass().getName() + " " + e.getMessage());
            }
            if (earAppName != null) {
                jStdCtx.setJ2EEApplication(earAppName);
            } else {
                jStdCtx.setJ2EEApplication("null");
            }
            jStdCtx.setJonasDeploymentDescriptor(webDD.getJOnASXmlContent());
            jStdCtx.setWebDeploymentDescriptor(webDD);

            // Set the realm
            org.apache.catalina.Realm ctxRealm = jStdCtx.getRealm();

            // Take realm of parent
            if (ctxRealm == null) {
                ctxRealm = deployer.getRealm();
            }

            if (ctxRealm != null && ctxRealm instanceof Realm) {
                Realm jaccRealm = null;
                try {
                    jaccRealm = (Realm) ((Realm) ctxRealm).clone();
                } catch (CloneNotSupportedException cnse) {
                    String err = "Cannot clone the realm used by the existing context or its parent realm";
                    getLogger().log(BasicLevel.ERROR, err + cnse.getMessage());
                    throw new JWebContainerServiceException(err, cnse);
                }
                if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                    getLogger().log(BasicLevel.DEBUG, "Setting permission manager to " + permissionManager);
                }
                jaccRealm.setPermissionManager(permissionManager);
                jaccRealm.setSecurityService(securityService);
                jaccRealm.setContext(jStdCtx);
                // Set the new realm
                jStdCtx.setRealm(jaccRealm);
            }

            // Set the attributes transfered from the JOnAS War MBean.
            // Note that 'java2DelegationModel' and 'inEarCase' attributes
            // are passed as arguments to the JOnASStandardContext's constructor
            jStdCtx.setWarURL(warURL);
            if (inEarCase) {
                jStdCtx.setEarURL(earURL);
            }

            // ... and start it or add it
            if (jStdCtx.isInServerXml()) {
                try {
                    jStdCtx.setLoader(null);
                    jStdCtx.start();
                } catch (LifecycleException lce) {
                    logger.error("Cannot start the context '" + jStdCtx + "'.", lce);
                    throw new JWebContainerServiceException("Cannot start the context '" + jStdCtx + "'.", lce);
                }
            } else {
                // add it
                if (deployer == null) {
                    logger.error("No deployer found for the deployment of the context '" + jStdCtx + "'.");
                    throw new JWebContainerServiceException("No deployer found for the deployment of the context '" + jStdCtx
                            + "'.");
                }
                deployer.addChild(jStdCtx);
            }

            // Finalize webservices deployment when the context is started
            if (this.getJAXWSService() != null) {
                // Tell the JAXWS Service that this context deployment is over
                getJAXWSService().finalizePOJODeployment(jStdCtx.getServletContext());
            }

            // ...and check if it is now configured
            checkStartedContext(jStdCtx, permissionManager);

            // Store WebModule ObjectName in Context
            try {
                ctx.rebind("WebModule", jStdCtx.createObjectName(getDomainName(), jStdCtx.getParentName()));
            } catch (Exception e) {
                // NamingException or Mbean related Exception
                // TODO i18n
                String err = "Cannot rebind WebModule ObjectName in Context";
                logger.error(err, e);
                throw new JWebContainerServiceException(err, e);
            }
            ContextFinder.addNonVersionedContext(contextRoot);

            try {
                if (isVersioningEnabled() && contextRoot.length() > 0 && userURI.length() > 0) {
                    if (contextRoot.charAt(0) != '/') {
                        contextRoot = '/' + contextRoot;
                    }
                    if (userURI.charAt(0) != '/') {
                        userURI = '/' + userURI;
                    }

                    if (!contextRoot.equals(userURI)) {
                        logger.info("Deploying versioned application " + userURI
                                + ", path for the added version is " + contextRoot);
                        String policy = getVersioningService().getDefaultDeploymentPolicy();
                        if (earAppName != null) {
                            ContextFinder.bindContextRoot(earAppName, userURI, jStdCtx, policy);
                        } else {
                            ContextFinder.bindContextRoot(URLUtils.urlToFile(warURL).getName(), userURI, jStdCtx, policy);
                        }
                    }
                }
            } catch (Exception e) {
                throw new JWebContainerServiceException("Failed binding versioned web context", e);
            }
        }
    }

    /**
     * @return a new {@link ContextConfig} for JOnAS.
     */
    protected ContextConfig createContextConfig() {
        JOnASContextConfig config = new JOnASContextConfig();
        config.setSecurityService(securityService);
        return config;
    }

    /**
     * Creates a new JOnAS Realm (JACC or JAAS).
     * @param name resource name or entry name (depends if a JACC or JAAS realm is required)
     * @param isJaccRealm <code>true</code> if a JACC realm is required, false for a JAAS realm.
     * @return a configured Realm.
     */
    public Realm createJOnASRealm(final String name, final boolean isJaccRealm) {
        Realm realm = new Realm();
        realm.setSecurityService(getSecurityService());
        if (isJaccRealm) {
            realm.setResourceName(name);
        } else {
            realm.setJaasEntry(name);
        }
        return realm;
    }

    /**
     * Check if there is a previous JOnASStandardContext which match the current
     * context If true, deploy our context into the configured context.
     * @param contextRoot name of the context that we want to find preconfigured
     *        contexts
     * @param fpackedWar file of the original war file
     * @param destDir name of the unpacked directory of the war file
     * @return true if a context was found and used
     */
    protected List<JOnASStandardContext> getConfiguredMatchingJonasContexts(final String contextRoot,
                                                                            final File fpackedWar,
                                                                            final String destDir) {
        List<JOnASStandardContext> jonasContexts = new ArrayList<JOnASStandardContext>();
        // Check contexts of all services to see if there is a configured
        // context.
        // that we must use instead of creating a new one.
        // If a context is matching, we set its docbase and start it.
        for (Context ctx : getContexts()) {
            // Continue if it is not a JOnAS context
            if (!(ctx instanceof JOnASStandardContext)) {
                continue;
            }
            JOnASStandardContext jStdCtx = (JOnASStandardContext) ctx;
            if (jStdCtx != null) {
                // Not created in server.xml file
                if (!jStdCtx.isInServerXml()) {
                    continue;
                }

                // The context was configured ?
                String serverCtxRoot = jStdCtx.getPath();
                if (!serverCtxRoot.equals(contextRoot)) {
                    continue;
                }
                if (jStdCtx.getPrivileged()) {
                    // Can deploy a privileged context only if it's a .war file
                    if (fpackedWar.isDirectory()) {
                        logger.error("Can deploy a privileged context '" + jStdCtx
                                + "' only if it's a war file (and not a directory)");
                    }
                    jStdCtx.setDocBase(fpackedWar.getPath());
                } else {
                    jStdCtx.setDocBase(destDir);
                }
                jonasContexts.add(jStdCtx);
            }
        }
        return jonasContexts;
    }

    /**
     * Check that the context that was started was right configured.
     * @param context context to check
     * @param permissionManager the permission manager used for JACC
     * @throws JWebContainerServiceException if the context was not right
     *         configured
     */
    protected void checkStartedContext(final Context context, final PermissionManager permissionManager)
            throws JWebContainerServiceException {
        // Check if the context was configured
        if (!context.getConfigured()) {
            throw new JWebContainerServiceException("Context '" + context + "' was not configured");
        }

        // Check that JACC realm is correctly set
        org.apache.catalina.Realm ctxRealm = context.getRealm();
        if (ctxRealm != null && ctxRealm instanceof Realm) {
            Realm jaccRealm = (Realm) ctxRealm;
            PermissionManager ctxPerm = jaccRealm.getPermissionManager();
            if (ctxPerm == null && permissionManager != null) {
                jaccRealm.setPermissionManager(permissionManager);
                // set context on the realm.
                jaccRealm.setContext(context);
            }
        }
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            getLogger().log(BasicLevel.DEBUG, "context " + context + " started");
        }

    }

    /**
     * Gets all the engines of the current Tomcat server.
     * @return all the engines of the current Tomcat server
     * @throws JWebContainerServiceException if engines can not be retrieved
     */
    protected synchronized List<StandardEngine> getEngines() throws JWebContainerServiceException {
        List<StandardEngine> engines = new ArrayList<StandardEngine>();
        Service[] services = getServer().findServices();

        // Get contexts of each engine of each service
        for (int s = 0; s < services.length; s++) {
            Container cont = services[s].getContainer();
            if (!(cont instanceof StandardEngine)) {
                String err = "The container of the service must be an engine (server.xml)";
                throw new JWebContainerServiceException(err);
            }
            engines.add((StandardEngine) cont);
        }
        return engines;
    }

    /**
     * Gets all the contexts of the current Tomcat server.
     * @return all the contexts of the current Tomcat server
     * @throws JWebContainerServiceException if contexts can not be retrieved
     */
    protected synchronized List<Context> getContexts() throws JWebContainerServiceException {
        List<Context> contexts = new ArrayList<Context>();
        for (Engine engine : getEngines()) {
            Container[] hosts = engine.findChildren();

            for (int j = 0; j < hosts.length; j++) {
                Container[] containers = hosts[j].findChildren();
                for (int k = 0; k < containers.length; k++) {
                    Container container = containers[k];
                    if (container instanceof Context) {
                        contexts.add((Context) container);
                    }
                }
            }
        }
        return contexts;
    }

    /**
     * Undeploy a specific WAR file specified in the context.
     * @param ctx the context which contains the configuration in order to
     *        undeploy a WAR.
     * @throws JWebContainerServiceException if the unregistration failed.
     */
    @Override
    protected void doUnRegisterWar(final javax.naming.Context ctx) throws JWebContainerServiceException {

        // Get the 2 parameters :
        // - contextRoot is the context root to be removed (required param).
        // - hostName is the name of the host to remove the war (optional).
        String contextRoot = null;
        try {
            contextRoot = (String) ctx.lookup("contextRoot");
        } catch (NamingException e) {
            throw new JWebContainerServiceException("Unable to find the contextRoot parameter", e);
        }

        // Change the root context to ""
        if (contextRoot.equals("/")) {
            contextRoot = "";
        } else {
            contextRoot = "/" + contextRoot;
        }
        ContextFinder.removeNonVersionedContext(contextRoot);

        String hostName = null;
        try {
            hostName = (String) ctx.lookup("hostName");
        } catch (NamingException e) {
            logger.debug("No hostname was defined for this context");
        }

        // Undeploy all matching context if there is no hostname provided
        if (hostName == null) {
            boolean found = false;
            for (Context context : getContexts()) {
                String serverCtxRoot = context.getPath();
                if (serverCtxRoot.equals(contextRoot)) {
                    removeContext(context);
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new JWebContainerServiceException("Unable to remove a context with the context name '"
                        + contextRoot + "'.");
            }
        } else {
            // Remove the specified Context from the set of defined Contexts
            // for its associated Host.
            Host host = findHost(hostName);
            org.apache.catalina.Context context = host.map(contextRoot);
            if (context != null) {
                removeContext(context);
            } else {
                throw new JWebContainerServiceException("Unable to find a context for the given hostname '" + hostName + "'.");
            }
        }

        try {
            if (isVersioningEnabled() && contextRoot.length() > 0) {
                if (contextRoot.charAt(0) != '/') {
                    contextRoot = '/' + contextRoot;
                }
                if (ContextFinder.unbindContextRoot(contextRoot)) {
                    logger.info("Undeployed version " + contextRoot);
                }
            }
        } catch (Exception e) {
            throw new JWebContainerServiceException("Failed unbinding web context", e);
        }
    }

    /**
     * Remove the specified Context from the set of defined Contexts for its
     * associated Host. If this is the last Context for this Host, the Host will
     * also be removed.
     * @param context The Context to be removed
     * @throws JWebContainerServiceException if the context can not be removed
     */
    public synchronized void removeContext(final Context context) throws JWebContainerServiceException {

        // Is this Context actually among those that are deployed ?
        boolean found = false;
        Iterator<Context> it = getContexts().iterator();
        while (it.hasNext() && !found) {
            if (context == it.next()) {
                found = true;
            }
        }

        if (!found) {
            return;
        }
        // Remove this Context from the associated Host
        // Or stop it if it is a JOnAS standard context (to keep the
        // configuration)
        if (context instanceof JOnASStandardContext) {
            JOnASStandardContext jctx = (JOnASStandardContext) context;
            if (jctx.isInServerXml()) {
                try {
                    ((JOnASStandardContext) context).stop();
                } catch (LifecycleException le) {
                    throw new JWebContainerServiceException("Cannot stop context (" + le.getMessage() + ")");
                }
            } else {
                // must not persist
                context.getParent().removeChild(context);
            }
        } else {
            context.getParent().removeChild(context);
            // Bug Tomcat 5 doesn't remove MBean WebModule
            // Delete the next line when will be fixed
            // Bug Tomcat 5 : begin
            if (context instanceof StandardContext) {
                StandardContext ctx = (StandardContext) context;
                try {
                    // unregister WebModule MBean
                    jmxService.getJmxServer().unregisterMBean(ctx.getJmxName());
                } catch (Exception e) {
                    getLogger().log(BasicLevel.ERROR,
                            "Cannot remove the MBean for the WebModule " + ctx.getObjectName() + " : " + e.getMessage());
                }
            }
        }

    }

    /**
     * The server is started ?
     * @return boolean true if the catalina container is running.
     */
    public boolean isTomcatStarted() {
        return tomcatStarted;
    }

    /**
     * Set the server instance we are configuring.
     * @param server The new server
     */
    public void setServer(final Server server) {
        this.server = server;
    }

    /**
     * @return the server instance
     */
    public synchronized Server getServer() {
        // Needs access to the server object
        if (server == null) {
            startInternalWebContainer();
        }
        return server;
    }


    /**
     * Return a File object representing the server.xml configuration file.
     * @return a File object representing the server.xml configuration file.
     * @throws FileNotFoundException if the configuration file is not found.
     */
    protected File getConfigFile() throws FileNotFoundException {
        String fileName = System.getProperty("catalina.base");
        fileName = fileName + File.separator + CONFIG_FILE;
        File file = new File(fileName);
        if (!file.exists()) {
            throw new FileNotFoundException("The configuration file '" + fileName + "' does not exists.");
        }
        return (file);
    }

    /**
     * Find the specified host.
     * @param hostName the name of the host to find.
     * @return the host found.
     * @throws JWebContainerServiceException if the specified host cannot be
     *         found.
     */
    public Host findHost(final String hostName) throws JWebContainerServiceException {

        Service[] services = getServer().findServices();
        // Check number of services
        if (services.length < 1) {
            String err = "At least one service must be define in the server.xml of Tomcat";
            throw new JWebContainerServiceException(err);
        }

        // Two cases :
        // 1/ No host specified
        // -> defaulthost of the engine of the first service.
        // 2) A host is specified in the jonas-web.xml
        // -> find the host and deploy on this host

        if (hostName == null || hostName.equals("")) {
            // First case

            // Take first service
            Service service = services[0];

            Container cont = service.getContainer();
            if (!(cont instanceof Engine)) {
                String err = "The container of the service must be an engine";
                throw new JWebContainerServiceException(err);
            }

            Engine engine = (Engine) cont;
            String defaultHost = engine.getDefaultHost();
            if (defaultHost == null) {
                String err = "Default host must be specified in server.xml or host must be specified in jonas-web.xml";
                throw new JWebContainerServiceException(err);
            }
            Container child = engine.findChild(defaultHost);
            // found, return it
            if (child instanceof Host) {
                return (Host) child;
            }
            // else error
            String err = "Default host " + defaultHost + " not found";
            throw new JWebContainerServiceException(err);
        }
        // Get all hosts.
        List<Host> hosts = new ArrayList<Host>();

        for (int s = 0; s < services.length; s++) {
            Container cont = services[s].getContainer();
            if (!(cont instanceof Engine)) {
                String err = "The container of a service must be an engine";
                throw new JWebContainerServiceException(err);
            }
            Engine engine = (Engine) cont;
            Container child = engine.findChild(hostName);
            if (child instanceof Host) {
                hosts.add((Host) child);
            }
        }

        // error
        if (hosts.size() == 0) {
            // No host found.
            String err = "Host " + hostName + " not found in all services/Engine of server.xml";
            throw new JWebContainerServiceException(err);
        }

        // first element
        return hosts.get(0);
    }

    /**
     * Create and configure the Digester that will be used for the xml parsing
     * of the configuration file.
     * @return Digester the digester containing the rules for the xml parsing of
     *         the server.xml.
     */
    protected Digester createServerDigester() {

        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.addRuleSet(new JCatalinaRuleSet(externalLoader, securityService));

        // Use context class loader.
        // Could avoid problem for users putting digester in JONAS_ROOT/lib/ext
        // folder
        digester.setUseContextClassLoader(true);
        return (digester);

    }

    /**
     * Update info of the serverName and serverVersion.
     */
    @Override
    protected void updateServerInfos() {
        String infos = org.apache.catalina.util.ServerInfo.getServerInfo();
        StringTokenizer st = new StringTokenizer(infos, "/");
        if (st.countTokens() != 2) {
            setServerName(infos);
            setServerVersion("");
        } else {
            setServerName(st.nextToken());
            setServerVersion(st.nextToken());
        }
    }

    /**
     * Return the Default host name of the web container.
     * @return the Default host name of the web container.
     * @throws JWebContainerServiceException when default host cannot be
     *         resolved (multiple services).
     */
    @Override
    public String getDefaultHost() throws JWebContainerServiceException {
        Engine engine = (Engine) getFirstService().getContainer();
        return engine.getDefaultHost();
    }

    /**
     * Return the Default HTTP port number of the web container. Returns the
     * first connector port if more than one are defined.
     * @return the Default HTTP port number of the web container.
     * @throws JWebContainerServiceException when default HTTP port cannot be
     *         resolved or none are defined.
     */
    @Override
    public String getDefaultHttpPort() throws JWebContainerServiceException {
        // if server not yet started, analyze the .xml file
        if (!tomcatStarted) {

            File configFile = null;
            try {
                configFile = getConfigFile();
            } catch (FileNotFoundException e) {
                throw new JWebContainerServiceException("Cannot get configuration file", e);
            }

            InputStream is = null;
            try {
                is = new FileInputStream(configFile);
                DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                domFactory.setNamespaceAware(true);
                DocumentBuilder builder = null;
                try {
                    builder = domFactory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                    throw new JWebContainerServiceException("Cannot build document builder", e);
                }

                Document doc = null;
                try {
                    doc = builder.parse(is);
                } catch (SAXException e) {
                    throw new JWebContainerServiceException("Cannot analyze configuration file", e);
                } catch (IOException e) {
                    throw new JWebContainerServiceException("Cannot analyze configuration file", e);
                }
                XPathFactory xPathFactory = XPathFactory.newInstance();
                XPath xPath = xPathFactory.newXPath();
                XPathExpression xPathExpr = null;
                try {
                    xPathExpr = xPath.compile("//Server/Service/Connector[1]");
                } catch (XPathExpressionException e) {
                    throw new JWebContainerServiceException("Cannot analyze configuration file", e);
                }
                Node node = null;
                try {
                    node = (Node) xPathExpr.evaluate(doc, XPathConstants.NODE);
                } catch (XPathExpressionException e) {
                    throw new JWebContainerServiceException("Cannot analyze configuration file", e);
                }
                if (node == null) {
                    throw new JWebContainerServiceException("No connector element found in the configuration file");
                }
                String text = node.getAttributes().getNamedItem("port").getNodeValue();
                return text;
            } catch (FileNotFoundException e) {
                throw new JWebContainerServiceException("Cannot get input stream", e);
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        logger.debug("Cannot close input stream", e);
                    }
                }
            }
        }

        return String.valueOf(getPort("http"));
    }

    /**
     * Return the Default HTTPS port number of the web container (can be null if
     * multiple HTTPS connector has been set).
     * @return the Default HTTPS port number of the web container.
     * @throws JWebContainerServiceException when default HTTPS port cannot be
     *         resolved (0 occurences).
     */
    @Override
    public String getDefaultHttpsPort() throws JWebContainerServiceException {
        return String.valueOf(getPort("https"));
    }

    /**
     * Finds the first connector supporting the given URL scheme
     * and return its listening port.
     * @param scheme URL schema (<tt>http</tt>/<tt>https</tt>)
     * @return Port number for a given scheme.
     * @throws JWebContainerServiceException if no connectors were found
     */
    protected int getPort(final String scheme) throws JWebContainerServiceException {
        Service svc = getFirstService();
        List<Connector> conn = new ArrayList<Connector>();
        for (Connector c : svc.findConnectors()) {
            if (c.getScheme().equalsIgnoreCase(scheme)) {
                conn.add(c);
            }
        }

        if (conn.isEmpty()) {
            // No connectors defined
            throw new JWebContainerServiceException("No Connectors found for scheme '" + scheme + "' in " + CONFIG_FILE);
        }

        Connector c = conn.get(0);

        // Warn the administrator if there are more than one connectors
        // specified for a single scheme.
        if (conn.size() > 1) {
            if (getLogger().isLoggable(BasicLevel.WARN)) {
                getLogger().log(
                        BasicLevel.WARN,
                        "Found multiple Connectors for scheme '" + scheme + "' in " + CONFIG_FILE
                                + ", using first by default! (port:" + c.getPort() + ")");
            }
        }

        return c.getPort();
    }

    /**
     * @return Returns the first found Service
     */
    protected Service getFirstService() {
        Service[] svc = getServer().findServices();
        // throw exception if no service is defined
        if ((svc == null) || (svc.length == 0)) {
            // No Services specified.
            throw new JWebContainerServiceException("No Services found in " + CONFIG_FILE);
        }
        // Warn the administrator if there are more than one services
        // specified in the configuration file.
        if (svc.length > 1) {
            if (getLogger().isLoggable(BasicLevel.WARN)) {
                getLogger().log(BasicLevel.WARN, "Found multiple Services in " + CONFIG_FILE + ", using first by default!");
            }
        }
        return svc[0];
    }

    /**
     * Register a WAR by delegating the operation to the registerWar() method.
     * This is used for JMX management.
     * @param fileName the name of the war to deploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the registration failed.
     */
    @Override
    public void registerWar(final String fileName) throws RemoteException, JWebContainerServiceException {
        ClassLoader old = null;
        try {
            old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(externalLoader);
            super.registerWar(fileName);
        } catch (Exception e) {
            throw new ServiceException("Exception during registering war", e);
        } finally {
            if (old != null) {
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    }

    /**
     * Unregister a WAR by delegating the operation to the unRegisterWar()
     * method. This is used for JMX management.
     * @param fileName the name of the war to undeploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the unregistration failed.
     */
    @Override
    public void unRegisterWar(final String fileName) throws RemoteException, JWebContainerServiceException {
        ClassLoader old = null;
        try {
            old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(externalLoader);
            super.unRegisterWar(fileName);
        } catch (Exception e) {
            throw new ServiceException("Exception during unregistering war", e);
        } finally {
            if (old != null) {
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    }

    /**
     * @param securityService Security service to set.
     */
    public void setSecurityService(final SecurityService securityService) {
        this.securityService = securityService;
    }

    /**
     * @return Security service.
     */
    protected SecurityService getSecurityService() {
        return securityService;
    }

        /**
     * @param transactionService TX service to set.
     */
    public void setTransactionService(final TransactionService transactionService) {
        this.transactionService = transactionService;
    }

    /**
     * @return TX service.
     */
    protected TransactionService getTransactionService() {
        return transactionService;
    }

    /**
     * @return the resource checker manager
     */
    public IResourceCheckerManager getResourceCheckerManager() {
        return resourceCheckerManager;
    }

    /**
     * Sets the resource checker manager.
     * @param resourceCheckerManager the given instance
     */
    public void setResourceCheckerManager(final IResourceCheckerManager resourceCheckerManager) {
        this.resourceCheckerManager = resourceCheckerManager;
    }

    /**
     * Unset the resource checker manager.
     */
    public void unsetResourceCheckerManager() {
        this.resourceCheckerManager = null;
    }

}
