<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.authenticator.NonLoginAuthenticator;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.NamingContextListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.session.StandardManager;

// TODO: lazy init for the temp dir - only when a JSP is compiled or 
// get temp dir is called we need to create it. This will avoid the 
// need for the baseDir

// TODO: allow contexts without a base dir - i.e. 
// only programmatic. This would disable the default servlet.

/**
 * Minimal tomcat starter for embedding/unit tests.
 * 
 * Tomcat supports multiple styles of configuration and 
 * startup - the most common and stable is server.xml-based,
 * implemented in org.apache.catalina.startup.Bootstrap.
 *
 * This class is for use in apps that embed tomcat. 
 * Requirements:
 * 
 * - all tomcat classes and possibly servlets are in the classpath.
 * ( for example all is in one big jar, or in eclipse CP, or in any other
 * combination )
 * 
 * - we need one temporary directory for work files
 * 
 * - no config file is required. This class provides methods to 
 * use if you have a webapp with a web.xml file, but it is 
 * optional - you can use your own servlets.
 * 
 * This class provides a main() and few simple CLI arguments,
 * see setters for doc. It can be used for simple tests and
 * demo.
 * 
 * @see <a href="http://svn.apache.org/repos/asf/tomcat/trunk/test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
 * @author <NAME>
 */
public class Tomcat {
    // Single engine, service, server, connector - few cases need more,
    // they can use server.xml
    protected Server server;
    protected Service service;
    protected Engine engine;
    protected Connector connector; // for more - customize the classes
    
    // To make it a bit easier to config for the common case
    // ( one host, one context ). 
    protected Host host;

    // TODO: it's easy to add support for more hosts - but is it 
    // really needed ?

    // TODO: allow use of in-memory connector
    
    protected int port = 8080;
    protected String hostname = "localhost";
    protected String basedir;
    
    // Default in-memory realm, will be set by default on 
    // created contexts. Can be replaced with setRealm() on 
    // the context.
    protected Realm defaultRealm;
    private Map<String, String> userPass = new HashMap<String, String>();
    private Map<String, List<String>> userRoles = 
            new HashMap<String, List<String>>();
    private Map<String, Principal> userPrincipals = new HashMap<String, Principal>();
    
    public Tomcat() {
        // NOOP
    }
    
    /**
     * Tomcat needs a directory for temp files. This should be the 
     * first method called. 
     * 
     * By default, if this method is not called, we use:
     *  - system properties - catalina.base, catalina.home 
     *  - $HOME/tomcat.$PORT
     * ( /tmp doesn't seem a good choice for security ).
     *   
     *
     * TODO: better default ? Maybe current dir ? 
     * TODO: disable work dir if not needed ( no jsp, etc ).
     */
    public void setBaseDir(String basedir) {
        this.basedir = basedir;
    }

    /** 
     * Set the port for the default connector. Must 
     * be called before start().
     */
    public void setPort(int port) {
        this.port = port;
    }
    
    /** 
     * The the hostname of the default host, default is 
     * 'localhost'.
     */
    public void setHostname(String s) {
        hostname = s;
    }

    /** 
     * Add a webapp using normal WEB-INF/web.xml if found.
     * 
     * @param contextPath
     * @param baseDir
     * @return new Context
     * @throws ServletException 
     */
    public Context addWebapp(String contextPath, 
                                     String baseDir) throws ServletException {
        
        return addWebapp(getHost(), contextPath, baseDir);    
    }
    
    
    /** 
     * Add a context - programmatic mode, no web.xml used.
     *
     * API calls equivalent with web.xml:
     * 
     * context-param
     *  ctx.addParameter("name", "value");
     *     
     *
     * error-page
     *    ErrorPage ep = new ErrorPage();
     *    ep.setErrorCode(500);
     *    ep.setLocation("/error.html");
     *    ctx.addErrorPage(ep);
     *   
     * ctx.addMimeMapping("ext", "type");
     * 
     * Note: If you reload the Context, all your configuration will be lost. If
     * you need reload support, consider using a LifecycleListener to provide
     * your configuration.
     *  
     * TODO: add the rest
     *
     *  @param contextPath "" for root context.
     *  @param baseDir base dir for the context, for static files. Must exist, 
     *  relative to the server home
     */
    public Context addContext(String contextPath, 
                                      String baseDir) {
        return addContext(getHost(), contextPath, baseDir);
    }

    /**
     * Equivalent with 
     *  <servlet><servlet-name><servlet-class>.
     *  
     * In general it is better/faster to use the method that takes a 
     * Servlet as param - this one can be used if the servlet is not 
     * commonly used, and want to avoid loading all deps.
     * ( for example: jsp servlet )
     * 
     * You can customize the returned servlet, ex:
     * 
     *    wrapper.addInitParameter("name", "value");
     *    
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    public Wrapper addServlet(String contextPath, 
            String servletName, 
            String servletClass) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servletClass);
    }

    /**
     * Static version of {@link #addServlet(String, String, String)}
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    public static Wrapper addServlet(Context ctx, 
                                      String servletName, 
                                      String servletClass) {
        // will do class for name and set init params
        Wrapper sw = ctx.createWrapper();
        sw.setServletClass(servletClass);
        sw.setName(servletName);
        ctx.addChild(sw);
        
        return sw;
    }

    /**
     * Add an existing Servlet to the context with no class.forName or
     * initialisation.
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public Wrapper addServlet(String contextPath, 
            String servletName, 
            Servlet servlet) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servlet);
    }

    /**
     * Static version of {@link #addServlet(String, String, Servlet)}.
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public static Wrapper addServlet(Context ctx,
                                      String servletName, 
                                      Servlet servlet) {
        // will do class for name and set init params
        Wrapper sw = new ExistingStandardWrapper(servlet);
        sw.setName(servletName);
        ctx.addChild(sw);
        
        return sw;
    }
    

    /**
     * Initialise the server.
     * 
     * @throws LifecycleException
     */
    public void init() throws LifecycleException {
        getServer();
        getConnector();
        server.init();
    }
    
    
    /**
     * Start the server.
     * 
     * @throws LifecycleException 
     */
    public void start() throws LifecycleException {
        getServer();
        getConnector();
        server.start();
    }

    /** 
     * Stop the server.
     * 
     * @throws LifecycleException 
     */
    public void stop() throws LifecycleException {
        getServer();
        server.stop();
    }


    /**
     * Destroy the server. This object cannot be used once this method has been
     * called.
     */
    public void destroy() throws LifecycleException {
        getServer();
        server.destroy();
        // Could null out objects here
    }
    
    /** 
     * Add a user for the in-memory realm. All created apps use this 
     * by default, can be replaced using setRealm().
     *  
     */
    public void addUser(String user, String pass) {
        userPass.put(user, pass);
    }
    
    /**
     * @see #addUser(String, String) 
     */
    public void addRole(String user, String role) {
        List<String> roles = userRoles.get(user);
        if (roles == null) {
            roles = new ArrayList<String>();
            userRoles.put(user, roles);
        }
        roles.add(role);
    }

    // ------- Extra customization -------
    // You can tune individual tomcat objects, using internal APIs
    
    /** 
     * Get the default http connector. You can set more 
     * parameters - the port is already initialized.
     * 
     * Alternatively, you can construct a Connector and set any params,
     * then call addConnector(Connector)
     * 
     * @return A connector object that can be customized
     */
    public Connector getConnector() {
        getServer();
        if (connector != null) {
            return connector;
        }
        // This will load Apr connector if available,
        // default to nio. I'm having strange problems with apr
        // XXX: jfclere weird... Don't add the AprLifecycleListener then.
        // and for the use case the speed benefit wouldn't matter.
        
        connector = new Connector("HTTP/1.1");
        // connector = new Connector("org.apache.coyote.http11.Http11Protocol"); 
        connector.setPort(port);
        service.addConnector( connector );
        return connector;
    }
    
    public void setConnector(Connector connector) {
        this.connector = connector;
    }
    
    /** 
     * Get the service object. Can be used to add more 
     * connectors and few other global settings.
     */
    public Service getService() {
        getServer();
        return service;
    }

    /** 
     * Sets the current host - all future webapps will
     * be added to this host. When tomcat starts, the 
     * host will be the default host.
     * 
     * @param host
     */
    public void setHost(Host host) {
        this.host = host;
    }
    
    public Host getHost() {
        if (host == null) {
            host = new StandardHost();
            host.setName(hostname);

            getEngine().addChild( host );
        }
        return host;
    }
    
    /** 
     * Set a custom realm for auth. If not called, a simple
     * default will be used, using an internal map.
     * 
     * Must be called before adding a context.
     */
    public void setDefaultRealm(Realm realm) {
        defaultRealm = realm;
    }
    

    /** 
     * Access to the engine, for further customization.
     */
    public Engine getEngine() {
        if(engine == null ) {
            getServer();
            engine = new StandardEngine();
            engine.setName( "Tomcat" );
            engine.setDefaultHost(hostname);
            service.setContainer(engine);
        }
        return engine;
    }

    /**
     * Get the server object. You can add listeners and few more
     * customizations. JNDI is disabled by default.  
     */
    public Server getServer() {
        
        if (server != null) {
            return server;
        }
        
        initBaseDir(); 
        
        System.setProperty("catalina.useNaming", "false");
        
        server = new StandardServer();
        server.setPort( -1 );
        
        service = new StandardService();
        service.setName("Tomcat");
        server.addService( service );
        return server;
    }

    public Context addContext(Host host, String contextPath, String dir) {
        silence(contextPath);
        Context ctx = new StandardContext();
        ctx.setPath( contextPath );
        ctx.setDocBase(dir);
        ctx.addLifecycleListener(new FixContextListener());
        
        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
        return ctx;
    }
    
    public Context addWebapp(Host host, String url, String path) {
        silence(url);

        Context ctx = new StandardContext();
        ctx.setPath( url );
        ctx.setDocBase(path);
        if (defaultRealm == null) {
            initSimpleAuth();
        }
        ctx.setRealm(defaultRealm);

        ctx.addLifecycleListener(new DefaultWebXmlListener());
        
        ContextConfig ctxCfg = new ContextConfig();
        ctx.addLifecycleListener(ctxCfg);
        
        // prevent it from looking ( if it finds one - it'll have dup error )
        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
        
        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }

        return ctx;
    }


    // ---------- Helper methods and classes -------------------
    
    /** 
     * Initialize an in-memory realm. You can replace it 
     * for contexts with a real one. 
     */
    protected void initSimpleAuth() {
        defaultRealm = new RealmBase() {
            @Override
            protected String getName() {
                return "Simple";
            }

            @Override
            protected String getPassword(String username) {
                return userPass.get(username);
            }

            @Override
            protected Principal getPrincipal(String username) {
                Principal p = userPrincipals.get(username);
                if (p == null) {
                    String pass = userPass.get(username);
                    if (pass != null) {
                        p = new GenericPrincipal(username, pass,
                                userRoles.get(username));
                        userPrincipals.put(username, p);
                    }
                }
                return p;
            }
            
        };        
    }
    
    protected void initBaseDir() {
        if (basedir == null) {
            basedir = System.getProperty(Globals.CATALINA_BASE_PROP);
        }
        if (basedir == null) {
            basedir = System.getProperty(Globals.CATALINA_HOME_PROP);
        }
        if (basedir == null) {
            // Create a temp dir.
            basedir = System.getProperty("user.dir") + 
                "/tomcat." + port;
            File home = new File(basedir);
            home.mkdir();
            if (!home.isAbsolute()) {
                try {
                    basedir = home.getCanonicalPath();
                } catch (IOException e) {
                    basedir = home.getAbsolutePath();
                }
            }
        }
        System.setProperty(Globals.CATALINA_HOME_PROP, basedir);
        System.setProperty(Globals.CATALINA_BASE_PROP, basedir);
    }

    static String[] silences = new String[] {
        "org.apache.coyote.http11.Http11Protocol",
        "org.apache.catalina.core.StandardService",
        "org.apache.catalina.core.StandardEngine",
        "org.apache.catalina.startup.ContextConfig",
        "org.apache.catalina.core.ApplicationContext",
        "org.apache.catalina.core.AprLifecycleListener"
    };
    
    /**
     * Controls if the loggers will be silenced or not.
     * @param silent    <code>true</code> sets the log level to WARN for the
     *                  loggers that log information on Tomcat start up. This
     *                  prevents the usual startup information being logged.
     *                  <code>false</code> sets the log level to the default
     *                  level of INFO.
     */
    public void setSilent(boolean silent) {
        for (String s : silences) {
            if (silent) {
                Logger.getLogger(s).setLevel(Level.WARNING);
            } else {
                Logger.getLogger(s).setLevel(Level.INFO);
            }
        }
    }
    
    private void silence(String ctx) {
        String base = "org.apache.catalina.core.ContainerBase.[default].[";
        base += getHost().getName();
        base += "].[";
        base += ctx; 
        base += "]";
        Logger.getLogger(base).setLevel(Level.WARNING);
    }
    
    /**
     * Enables JNDI naming which is disabled by default. Server must implement
     * {@link Lifecycle} in order for the {@link NamingContextListener} to be
     * used.
     * 
     */
    public void enableNaming() {
        // Make sure getServer() has been called as that is where naming is
        // disabled
        getServer();
        server.addLifecycleListener(new NamingContextListener());
        
        System.setProperty("catalina.useNaming", "true");

        String value = "org.apache.naming";
        String oldValue =
            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
        if (oldValue != null) {
            if (oldValue.contains(value)) {
                value = oldValue;
            } else {
                value = value + ":" + oldValue;
            }
        }
        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);

        value = System.getProperty
            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
        if (value == null) {
            System.setProperty
                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                 "org.apache.naming.java.javaURLContextFactory");
        }
    }

    /**
     * Provide default configuration for a context. This is the programmatic
     * equivalent of the default web.xml. 
     * 
     *  TODO: in normal Tomcat, if default-web.xml is not found, use this 
     *  method
     *  
     * @param contextPath   The context to set the defaults for
     */
    public void initWebappDefaults(String contextPath) {
        Container ctx = getHost().findChild(contextPath);
        initWebappDefaults((Context) ctx);
    }
    
    /**
     * Static version of {@link #initWebappDefaults(String)}
     * @param ctx   The context to set the defaults for
     */
    public static void initWebappDefaults(Context ctx) {
        // Default servlet 
        Wrapper servlet = addServlet(
                ctx, "default", "org.apache.catalina.servlets.DefaultServlet");
        servlet.setLoadOnStartup(1);

        // JSP servlet (by class name - to avoid loading all deps)
        servlet = addServlet(
                ctx, "jsp", "org.apache.jasper.servlet.JspServlet");
        servlet.addInitParameter("fork", "false");
        servlet.setLoadOnStartup(3);
        
        // Servlet mappings 
        ctx.addServletMapping("/", "default");
        ctx.addServletMapping("*.jsp", "jsp");
        ctx.addServletMapping("*.jspx", "jsp");

        // Sessions
        ctx.setManager( new StandardManager());
        ctx.setSessionTimeout(30);
        
        // MIME mappings
        for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length;) {
            ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++],
                    DEFAULT_MIME_MAPPINGS[i++]);
        }
        
        // Welcome files
        ctx.addWelcomeFile("index.html");
        ctx.addWelcomeFile("index.htm");
        ctx.addWelcomeFile("index.jsp");
    }

    
    /**
     * Fix startup sequence - required if you don't use web.xml.
     * 
     * The start() method in context will set 'configured' to false - and
     * expects a listener to set it back to true.
     */
    public static class FixContextListener implements LifecycleListener {

        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            try {
                Context context = (Context) event.getLifecycle();
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                    context.setConfigured(true);
                }
                // LoginConfig is required to process @ServletSecurity
                // annotations
                if (context.getLoginConfig() == null) {
                    context.setLoginConfig(
                            new LoginConfig("NONE", null, null, null));
                    context.getPipeline().addValve(new NonLoginAuthenticator());
                }
            } catch (ClassCastException e) {
                return;
            }
        }
        
    }


    /**
     * Fix reload - required if reloading and using programmatic configuration.
     * When a context is reloaded, any programmatic configuration is lost. This
     * listener sets the equivalent of conf/web.xml when the context starts.
     */
    public static class DefaultWebXmlListener implements LifecycleListener {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
                initWebappDefaults((Context) event.getLifecycle());
            }
        }
    }


    /**
     * Helper class for wrapping existing servlets. This disables servlet 
     * lifecycle and normal reloading, but also reduces overhead and provide
     * more direct control over the servlet.  
     */
    public static class ExistingStandardWrapper extends StandardWrapper {
        private Servlet existing;
        boolean init = false;
        
        public ExistingStandardWrapper( Servlet existing ) {
            this.existing = existing;
        }
        @Override
        public synchronized Servlet loadServlet() throws ServletException {
            if (!init) {
                existing.init(facade);
                init = true;
            }
            return existing;

        }
        @Override
        public long getAvailable() {
            return 0;
        }
        @Override
        public boolean isUnavailable() {
            return false;       
        }
    }
    
    /**
     * TODO: would a properties resource be better ? Or just parsing
     * /etc/mime.types ?
     * This is needed because we don't use the default web.xml, where this 
     * is encoded.
     */
    private static final String[] DEFAULT_MIME_MAPPINGS = {
        "abs", "audio/x-mpeg", 
        "ai", "application/postscript", 
        "aif", "audio/x-aiff", 
        "aifc", "audio/x-aiff", 
        "aiff", "audio/x-aiff", 
        "aim", "application/x-aim", 
        "art", "image/x-jg", 
        "asf", "video/x-ms-asf", 
        "asx", "video/x-ms-asf", 
        "au", "audio/basic", <commit_msg>Fix some unit test failures<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.authenticator.NonLoginAuthenticator;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.NamingContextListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.session.StandardManager;

// TODO: lazy init for the temp dir - only when a JSP is compiled or 
// get temp dir is called we need to create it. This will avoid the 
// need for the baseDir

// TODO: allow contexts without a base dir - i.e. 
// only programmatic. This would disable the default servlet.

/**
 * Minimal tomcat starter for embedding/unit tests.
 * 
 * Tomcat supports multiple styles of configuration and 
 * startup - the most common and stable is server.xml-based,
 * implemented in org.apache.catalina.startup.Bootstrap.
 *
 * This class is for use in apps that embed tomcat. 
 * Requirements:
 * 
 * - all tomcat classes and possibly servlets are in the classpath.
 * ( for example all is in one big jar, or in eclipse CP, or in any other
 * combination )
 * 
 * - we need one temporary directory for work files
 * 
 * - no config file is required. This class provides methods to 
 * use if you have a webapp with a web.xml file, but it is 
 * optional - you can use your own servlets.
 * 
 * This class provides a main() and few simple CLI arguments,
 * see setters for doc. It can be used for simple tests and
 * demo.
 * 
 * @see <a href="http://svn.apache.org/repos/asf/tomcat/trunk/test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
 * @author <NAME>
 */
public class Tomcat {
    // Single engine, service, server, connector - few cases need more,
    // they can use server.xml
    protected Server server;
    protected Service service;
    protected Engine engine;
    protected Connector connector; // for more - customize the classes
    
    // To make it a bit easier to config for the common case
    // ( one host, one context ). 
    protected Host host;

    // TODO: it's easy to add support for more hosts - but is it 
    // really needed ?

    // TODO: allow use of in-memory connector
    
    protected int port = 8080;
    protected String hostname = "localhost";
    protected String basedir;
    
    // Default in-memory realm, will be set by default on 
    // created contexts. Can be replaced with setRealm() on 
    // the context.
    protected Realm defaultRealm;
    private Map<String, String> userPass = new HashMap<String, String>();
    private Map<String, List<String>> userRoles = 
            new HashMap<String, List<String>>();
    private Map<String, Principal> userPrincipals = new HashMap<String, Principal>();
    
    public Tomcat() {
        // NOOP
    }
    
    /**
     * Tomcat needs a directory for temp files. This should be the 
     * first method called. 
     * 
     * By default, if this method is not called, we use:
     *  - system properties - catalina.base, catalina.home 
     *  - $HOME/tomcat.$PORT
     * ( /tmp doesn't seem a good choice for security ).
     *   
     *
     * TODO: better default ? Maybe current dir ? 
     * TODO: disable work dir if not needed ( no jsp, etc ).
     */
    public void setBaseDir(String basedir) {
        this.basedir = basedir;
    }

    /** 
     * Set the port for the default connector. Must 
     * be called before start().
     */
    public void setPort(int port) {
        this.port = port;
    }
    
    /** 
     * The the hostname of the default host, default is 
     * 'localhost'.
     */
    public void setHostname(String s) {
        hostname = s;
    }

    /** 
     * Add a webapp using normal WEB-INF/web.xml if found.
     * 
     * @param contextPath
     * @param baseDir
     * @return new Context
     * @throws ServletException 
     */
    public Context addWebapp(String contextPath, 
                                     String baseDir) throws ServletException {
        
        return addWebapp(getHost(), contextPath, baseDir);    
    }
    
    
    /** 
     * Add a context - programmatic mode, no web.xml used.
     *
     * API calls equivalent with web.xml:
     * 
     * context-param
     *  ctx.addParameter("name", "value");
     *     
     *
     * error-page
     *    ErrorPage ep = new ErrorPage();
     *    ep.setErrorCode(500);
     *    ep.setLocation("/error.html");
     *    ctx.addErrorPage(ep);
     *   
     * ctx.addMimeMapping("ext", "type");
     * 
     * Note: If you reload the Context, all your configuration will be lost. If
     * you need reload support, consider using a LifecycleListener to provide
     * your configuration.
     *  
     * TODO: add the rest
     *
     *  @param contextPath "" for root context.
     *  @param baseDir base dir for the context, for static files. Must exist, 
     *  relative to the server home
     */
    public Context addContext(String contextPath, 
                                      String baseDir) {
        return addContext(getHost(), contextPath, baseDir);
    }

    /**
     * Equivalent with 
     *  <servlet><servlet-name><servlet-class>.
     *  
     * In general it is better/faster to use the method that takes a 
     * Servlet as param - this one can be used if the servlet is not 
     * commonly used, and want to avoid loading all deps.
     * ( for example: jsp servlet )
     * 
     * You can customize the returned servlet, ex:
     * 
     *    wrapper.addInitParameter("name", "value");
     *    
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    public Wrapper addServlet(String contextPath, 
            String servletName, 
            String servletClass) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servletClass);
    }

    /**
     * Static version of {@link #addServlet(String, String, String)}
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    public static Wrapper addServlet(Context ctx, 
                                      String servletName, 
                                      String servletClass) {
        // will do class for name and set init params
        Wrapper sw = ctx.createWrapper();
        sw.setServletClass(servletClass);
        sw.setName(servletName);
        ctx.addChild(sw);
        
        return sw;
    }

    /**
     * Add an existing Servlet to the context with no class.forName or
     * initialisation.
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public Wrapper addServlet(String contextPath, 
            String servletName, 
            Servlet servlet) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servlet);
    }

    /**
     * Static version of {@link #addServlet(String, String, Servlet)}.
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    public static Wrapper addServlet(Context ctx,
                                      String servletName, 
                                      Servlet servlet) {
        // will do class for name and set init params
        Wrapper sw = new ExistingStandardWrapper(servlet);
        sw.setName(servletName);
        ctx.addChild(sw);
        
        return sw;
    }
    

    /**
     * Initialise the server.
     * 
     * @throws LifecycleException
     */
    public void init() throws LifecycleException {
        getServer();
        getConnector();
        server.init();
    }
    
    
    /**
     * Start the server.
     * 
     * @throws LifecycleException 
     */
    public void start() throws LifecycleException {
        getServer();
        getConnector();
        server.start();
    }

    /** 
     * Stop the server.
     * 
     * @throws LifecycleException 
     */
    public void stop() throws LifecycleException {
        getServer();
        server.stop();
    }


    /**
     * Destroy the server. This object cannot be used once this method has been
     * called.
     */
    public void destroy() throws LifecycleException {
        getServer();
        server.destroy();
        // Could null out objects here
    }
    
    /** 
     * Add a user for the in-memory realm. All created apps use this 
     * by default, can be replaced using setRealm().
     *  
     */
    public void addUser(String user, String pass) {
        userPass.put(user, pass);
    }
    
    /**
     * @see #addUser(String, String) 
     */
    public void addRole(String user, String role) {
        List<String> roles = userRoles.get(user);
        if (roles == null) {
            roles = new ArrayList<String>();
            userRoles.put(user, roles);
        }
        roles.add(role);
    }

    // ------- Extra customization -------
    // You can tune individual tomcat objects, using internal APIs
    
    /** 
     * Get the default http connector. You can set more 
     * parameters - the port is already initialized.
     * 
     * Alternatively, you can construct a Connector and set any params,
     * then call addConnector(Connector)
     * 
     * @return A connector object that can be customized
     */
    public Connector getConnector() {
        getServer();
        if (connector != null) {
            return connector;
        }
        // This will load Apr connector if available,
        // default to nio. I'm having strange problems with apr
        // XXX: jfclere weird... Don't add the AprLifecycleListener then.
        // and for the use case the speed benefit wouldn't matter.
        
        connector = new Connector("HTTP/1.1");
        // connector = new Connector("org.apache.coyote.http11.Http11Protocol"); 
        connector.setPort(port);
        service.addConnector( connector );
        return connector;
    }
    
    public void setConnector(Connector connector) {
        this.connector = connector;
    }
    
    /** 
     * Get the service object. Can be used to add more 
     * connectors and few other global settings.
     */
    public Service getService() {
        getServer();
        return service;
    }

    /** 
     * Sets the current host - all future webapps will
     * be added to this host. When tomcat starts, the 
     * host will be the default host.
     * 
     * @param host
     */
    public void setHost(Host host) {
        this.host = host;
    }
    
    public Host getHost() {
        if (host == null) {
            host = new StandardHost();
            host.setName(hostname);

            getEngine().addChild( host );
        }
        return host;
    }
    
    /** 
     * Set a custom realm for auth. If not called, a simple
     * default will be used, using an internal map.
     * 
     * Must be called before adding a context.
     */
    public void setDefaultRealm(Realm realm) {
        defaultRealm = realm;
    }
    

    /** 
     * Access to the engine, for further customization.
     */
    public Engine getEngine() {
        if(engine == null ) {
            getServer();
            engine = new StandardEngine();
            engine.setName( "Tomcat" );
            engine.setDefaultHost(hostname);
            service.setContainer(engine);
        }
        return engine;
    }

    /**
     * Get the server object. You can add listeners and few more
     * customizations. JNDI is disabled by default.  
     */
    public Server getServer() {
        
        if (server != null) {
            return server;
        }
        
        initBaseDir(); 
        
        System.setProperty("catalina.useNaming", "false");
        
        server = new StandardServer();
        server.setPort( -1 );
        
        service = new StandardService();
        service.setName("Tomcat");
        server.addService( service );
        return server;
    }

    public Context addContext(Host host, String contextPath, String dir) {
        silence(contextPath);
        Context ctx = new StandardContext();
        ctx.setPath( contextPath );
        ctx.setDocBase(dir);
        ctx.addLifecycleListener(new FixContextListener());
        
        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
        return ctx;
    }
    
    public Context addWebapp(Host host, String url, String path) {
        silence(url);

        Context ctx = new StandardContext();
        ctx.setPath( url );
        ctx.setDocBase(path);
        if (defaultRealm == null) {
            initSimpleAuth();
        }
        ctx.setRealm(defaultRealm);

        ctx.addLifecycleListener(new DefaultWebXmlListener());
        
        ContextConfig ctxCfg = new ContextConfig();
        ctx.addLifecycleListener(ctxCfg);
        
        // prevent it from looking ( if it finds one - it'll have dup error )
        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
        
        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }

        return ctx;
    }


    // ---------- Helper methods and classes -------------------
    
    /** 
     * Initialize an in-memory realm. You can replace it 
     * for contexts with a real one. 
     */
    protected void initSimpleAuth() {
        defaultRealm = new RealmBase() {
            @Override
            protected String getName() {
                return "Simple";
            }

            @Override
            protected String getPassword(String username) {
                return userPass.get(username);
            }

            @Override
            protected Principal getPrincipal(String username) {
                Principal p = userPrincipals.get(username);
                if (p == null) {
                    String pass = userPass.get(username);
                    if (pass != null) {
                        p = new GenericPrincipal(username, pass,
                                userRoles.get(username));
                        userPrincipals.put(username, p);
                    }
                }
                return p;
            }
            
        };        
    }
    
    protected void initBaseDir() {
        if (basedir == null) {
            basedir = System.getProperty(Globals.CATALINA_BASE_PROP);
        }
        if (basedir == null) {
            basedir = System.getProperty(Globals.CATALINA_HOME_PROP);
        }
        if (basedir == null) {
            // Create a temp dir.
            basedir = System.getProperty("user.dir") + 
                "/tomcat." + port;
            File home = new File(basedir);
            home.mkdir();
            if (!home.isAbsolute()) {
                try {
                    basedir = home.getCanonicalPath();
                } catch (IOException e) {
                    basedir = home.getAbsolutePath();
                }
            }
        }
        System.setProperty(Globals.CATALINA_HOME_PROP, basedir);
        System.setProperty(Globals.CATALINA_BASE_PROP, basedir);
    }

    static String[] silences = new String[] {
        "org.apache.coyote.http11.Http11Protocol",
        "org.apache.catalina.core.StandardService",
        "org.apache.catalina.core.StandardEngine",
        "org.apache.catalina.startup.ContextConfig",
        "org.apache.catalina.core.ApplicationContext",
        "org.apache.catalina.core.AprLifecycleListener"
    };
    
    /**
     * Controls if the loggers will be silenced or not.
     * @param silent    <code>true</code> sets the log level to WARN for the
     *                  loggers that log information on Tomcat start up. This
     *                  prevents the usual startup information being logged.
     *                  <code>false</code> sets the log level to the default
     *                  level of INFO.
     */
    public void setSilent(boolean silent) {
        for (String s : silences) {
            if (silent) {
                Logger.getLogger(s).setLevel(Level.WARNING);
            } else {
                Logger.getLogger(s).setLevel(Level.INFO);
            }
        }
    }
    
    private void silence(String ctx) {
        String base = "org.apache.catalina.core.ContainerBase.[default].[";
        base += getHost().getName();
        base += "].[";
        base += ctx; 
        base += "]";
        Logger.getLogger(base).setLevel(Level.WARNING);
    }
    
    /**
     * Enables JNDI naming which is disabled by default. Server must implement
     * {@link Lifecycle} in order for the {@link NamingContextListener} to be
     * used.
     * 
     */
    public void enableNaming() {
        // Make sure getServer() has been called as that is where naming is
        // disabled
        getServer();
        server.addLifecycleListener(new NamingContextListener());
        
        System.setProperty("catalina.useNaming", "true");

        String value = "org.apache.naming";
        String oldValue =
            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
        if (oldValue != null) {
            if (oldValue.contains(value)) {
                value = oldValue;
            } else {
                value = value + ":" + oldValue;
            }
        }
        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);

        value = System.getProperty
            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
        if (value == null) {
            System.setProperty
                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                 "org.apache.naming.java.javaURLContextFactory");
        }
    }

    /**
     * Provide default configuration for a context. This is the programmatic
     * equivalent of the default web.xml. 
     * 
     *  TODO: in normal Tomcat, if default-web.xml is not found, use this 
     *  method
     *  
     * @param contextPath   The context to set the defaults for
     */
    public void initWebappDefaults(String contextPath) {
        Container ctx = getHost().findChild(contextPath);
        initWebappDefaults((Context) ctx);
    }
    
    /**
     * Static version of {@link #initWebappDefaults(String)}
     * @param ctx   The context to set the defaults for
     */
    public static void initWebappDefaults(Context ctx) {
        // Default servlet 
        Wrapper servlet = addServlet(
                ctx, "default", "org.apache.catalina.servlets.DefaultServlet");
        servlet.setLoadOnStartup(1);

        // JSP servlet (by class name - to avoid loading all deps)
        servlet = addServlet(
                ctx, "jsp", "org.apache.jasper.servlet.JspServlet");
        servlet.addInitParameter("fork", "false");
        servlet.setLoadOnStartup(3);
        
        // Servlet mappings 
        ctx.addServletMapping("/", "default");
        ctx.addServletMapping("*.jsp", "jsp");
        ctx.addServletMapping("*.jspx", "jsp");

        // Sessions
        ctx.setManager( new StandardManager());
        ctx.setSessionTimeout(30);
        
        // MIME mappings
        for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length;) {
            ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++],
                    DEFAULT_MIME_MAPPINGS[i++]);
        }
        
        // Welcome files
        ctx.addWelcomeFile("index.html");
        ctx.addWelcomeFile("index.htm");
        ctx.addWelcomeFile("index.jsp");
    }

    
    /**
     * Fix startup sequence - required if you don't use web.xml.
     * 
     * The start() method in context will set 'configured' to false - and
     * expects a listener to set it back to true.
     */
    public static class FixContextListener implements LifecycleListener {

        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            try {
                Context context = (Context) event.getLifecycle();
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                    context.setConfigured(true);
                }
                // LoginConfig is required to process @ServletSecurity
                // annotations
                if (context.getLoginConfig() == null) {
                    context.setLoginConfig(
                            new LoginConfig("NONE", null, null, null));
                    context.getPipeline().addValve(new NonLoginAuthenticator());
                }
            } catch (ClassCastException e) {
                return;
            }
        }
        
    }


    /**
     * Fix reload - required if reloading and using programmatic configuration.
     * When a context is reloaded, any programmatic configuration is lost. This
     * listener sets the equivalent of conf/web.xml when the context starts.
     */
    public static class DefaultWebXmlListener implements LifecycleListener {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
                initWebappDefaults((Context) event.getLifecycle());
            }
        }
    }


    /**
     * Helper class for wrapping existing servlets. This disables servlet 
     * lifecycle and normal reloading, but also reduces overhead and provide
     * more direct control over the servlet.  
     */
    public static class ExistingStandardWrapper extends StandardWrapper {
        private Servlet existing;
        boolean init = false;
        
        public ExistingStandardWrapper( Servlet existing ) {
            this.existing = existing;
        }
        @Override
        public synchronized Servlet loadServlet() throws ServletException {
            if (!init) {
                existing.init(facade);
                init = true;
            }
            return existing;

        }
        @Override
        public long getAvailable() {
            return 0;
        }
        @Override
        public boolean isUnavailable() {
            return false;       
        }
        @Override
        public Servlet getServlet() {
            return existing;
        }
        @Override
        public String getServletClass() {
            return existing.getClass().getName();
        }
    }
    
    /**
     * TODO: would a properties resource be better ? Or just parsing
     * /etc/mime.types ?
     * This is needed because we don't use the default web.xml, where this 
     * is encoded.
     */
    private static final String[] DEFAULT_MIME_MAPPINGS = {
        "abs", "audio/x-mpeg", 
        "ai", "application/postscript", 
        "aif", "audio/x-aiff", 
        "aifc", "audio/x-aiff", 
        "aiff", "audio/x-aiff", 
        "aim", "application/x-aim", 
        "art", "image/x-jg", 
        "asf", "video/x-ms-asf", 
        "asx", "video/x-ms-asf", 
        "au", "audio/basic", 
{'fraction_non_alphanumeric': 0.09475539012849195, 'fraction_numerical': 0.0011087132986200478, 'mean_word_length': 1.916113388372496, 'pattern_counts': {'":': 2, '<': 45, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 22}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7310685', 'n_tokens_mistral': 13970, 'n_tokens_neox': 13258, 'n_words': 4110}