All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.startup.Tomcat Maven / Gradle / Ivy

There is a newer version: 11.0.1
Show newest version
/*
 * 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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;

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.ContainerBase;
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.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.util.ContextName;
import org.apache.catalina.util.IOTools;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.file.ConfigFileLoader;
import org.apache.tomcat.util.file.ConfigurationSource;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;

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

* There are a variety of 'add' methods to configure servlets and webapps. These methods, by default, create a simple * in-memory security realm and apply it. If you need more complex security processing, you can define a subclass of * this class. *

* This class provides a set of convenience methods for configuring web application contexts; all overloads of the * method addWebapp(). These methods are equivalent to adding a web application to the Host's appBase * (normally the webapps directory). These methods create a Context, configure it with the equivalent of the defaults * provided by conf/web.xml (see {@link #initWebappDefaults(String)} for details) and add the Context to a * Host. These methods do not use a global default web.xml; rather, they add a {@link LifecycleListener} to configure * the defaults. Any WEB-INF/web.xml and META-INF/context.xml packaged with the application will be processed normally. * Normal web fragment and {@link jakarta.servlet.ServletContainerInitializer} processing will be applied. *

* In complex cases, you may prefer to use the ordinary Tomcat API to create webapp contexts; for example, you might * need to install a custom Loader before the call to {@link Host#addChild(Container)}. To replicate the basic behavior * of the addWebapp methods, you may want to call two methods of this class: {@link #noDefaultWebXmlPath()} * and {@link #getDefaultWebXmlListener()}. *

* {@link #getDefaultWebXmlListener()} returns a {@link LifecycleListener} that adds the standard DefaultServlet, JSP * processing, and welcome files. If you add this listener, you must prevent Tomcat from applying any standard global * web.xml with ... *

* {@link #noDefaultWebXmlPath()} returns a dummy pathname to configure to prevent {@link ContextConfig} from trying to * apply a global web.xml file. *

* This class provides a main() and few simple CLI arguments, see setters for doc. It can be used for simple tests and * demo. * * @see TestTomcat * * @author Costin Manolache */ public class Tomcat { private static final StringManager sm = StringManager.getManager(Tomcat.class); // Some logging implementations use weak references for loggers so there is // the possibility that logging configuration could be lost if GC runs just // after Loggers are configured but before they are used. The purpose of // this Map is to retain strong references to explicitly configured loggers // so that configuration is not lost. private final Map pinnedLoggers = new HashMap<>(); protected Server server; protected int port = 8080; protected String hostname = "localhost"; protected String basedir; private final Map userPass = new HashMap<>(); private final Map> userRoles = new HashMap<>(); private final Map userPrincipals = new HashMap<>(); private boolean addDefaultWebXmlToWebapp = true; public Tomcat() { ExceptionUtils.preload(); } /** * Tomcat requires that the base directory is set because the defaults for a number of other locations, such as the * work directory, are derived from the base directory. This should be the first method called. *

* If this method is not called then Tomcat will attempt to use these locations in the following order: *

    *
  1. if set, the catalina.base system property
  2. *
  3. if set, the catalina.home system property
  4. *
  5. The user.dir system property (the directory where Java was run from) where a directory named tomcat.$PORT * will be created. $PORT is the value configured via {@link #setPort(int)} which defaults to 8080 if not set
  6. *
* The user should ensure that the file permissions for the base directory are appropriate. *

* TODO: disable work dir if not needed ( no jsp, etc ). * * @param basedir The Tomcat base folder on which all others will be derived */ public void setBaseDir(String basedir) { this.basedir = basedir; } /** * Set the port for the default connector. The default connector will only be created if getConnector is called. * * @param port The port number */ public void setPort(int port) { this.port = port; } /** * The the hostname of the default host, default is 'localhost'. * * @param s The default host name */ public void setHostname(String s) { hostname = s; } /** * This is equivalent to adding a web application to a Host's appBase (usually Tomcat's webapps directory). By * default, the equivalent of the default web.xml will be applied to the web application (see * {@link #initWebappDefaults(String)}). This may be prevented by calling * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any WEB-INF/web.xml and * META-INF/context.xml packaged with the application will always be processed and normal web fragment * and {@link jakarta.servlet.ServletContainerInitializer} processing will always be applied. * * @param contextPath The context mapping to use, "" for root context. * @param docBase Base directory for the context, for static files. Must exist and be an absolute path. * * @return the deployed context */ public Context addWebapp(String contextPath, String docBase) { return addWebapp(getHost(), contextPath, docBase); } /** * Copy the specified WAR file to the Host's appBase and then call {@link #addWebapp(String, String)} with the newly * copied WAR. The WAR will NOT be removed from the Host's appBase when the Tomcat instance stops. Note that * {@link ExpandWar} provides utility methods that may be used to delete the WAR and/or expanded directory if * required. * * @param contextPath The context mapping to use, "" for root context. * @param source The location from which the WAR should be copied * * @return The deployed Context * * @throws IOException If an I/O error occurs while copying the WAR file from the specified URL to the appBase */ public Context addWebapp(String contextPath, URL source) throws IOException { ContextName cn = new ContextName(contextPath, null); // Make sure a conflicting web application has not already been deployed Host h = getHost(); if (h.findChild(cn.getName()) != null) { throw new IllegalArgumentException( sm.getString("tomcat.addWebapp.conflictChild", source, contextPath, cn.getName())); } // Make sure appBase does not contain a conflicting web application File targetWar = new File(h.getAppBaseFile(), cn.getBaseName() + ".war"); File targetDir = new File(h.getAppBaseFile(), cn.getBaseName()); if (targetWar.exists()) { throw new IllegalArgumentException( sm.getString("tomcat.addWebapp.conflictFile", source, contextPath, targetWar.getAbsolutePath())); } if (targetDir.exists()) { throw new IllegalArgumentException( sm.getString("tomcat.addWebapp.conflictFile", source, contextPath, targetDir.getAbsolutePath())); } // Should be good to copy the WAR now URLConnection uConn = source.openConnection(); try (InputStream is = uConn.getInputStream(); OutputStream os = new FileOutputStream(targetWar)) { IOTools.flow(is, os); } return addWebapp(contextPath, targetWar.getAbsolutePath()); } /** * Add a context - programmatic mode, no default web.xml used. This means that there is no JSP support (no JSP * servlet), no default servlet and no web socket support unless explicitly enabled via the programmatic interface. * There is also no {@link jakarta.servlet.ServletContainerInitializer} processing and no annotation processing. If * a {@link jakarta.servlet.ServletContainerInitializer} is added programmatically, there will still be no scanning * for {@link jakarta.servlet.annotation.HandlesTypes} matches. *

* API calls equivalent with web.xml: * *

{@code
     * // 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 The context mapping to use, "" for root context. * @param docBase Base directory for the context, for static files. Must exist, relative to the server home * * @return the deployed context */ public Context addContext(String contextPath, String docBase) { return addContext(getHost(), contextPath, docBase); } /** * Equivalent to <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(); if (sw == null) { throw new IllegalStateException(sm.getString("tomcat.noWrapper")); } 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; } /** * Initialize the server given the specified configuration source. The server will be loaded according to the Tomcat * configuration files contained in the source (server.xml, web.xml, context.xml, SSL certificates, etc). If no * configuration source is specified, it will use the default locations for these files. * * @param source The configuration source */ public void init(ConfigurationSource source) { init(source, null); } /** * Initialize the server given the specified configuration source. The server will be loaded according to the Tomcat * configuration files contained in the source (server.xml, web.xml, context.xml, SSL certificates, etc). If no * configuration source is specified, it will use the default locations for these files. * * @param source The configuration source * @param catalinaArguments The arguments that should be passed to Catalina */ public void init(ConfigurationSource source, String[] catalinaArguments) { ConfigFileLoader.setSource(source); addDefaultWebXmlToWebapp = false; Catalina catalina = new Catalina(); // Load the Catalina instance with the regular configuration files // from specified source if (catalinaArguments == null) { catalina.load(); } else { catalina.load(catalinaArguments); } // Retrieve and set the server server = catalina.getServer(); } /** * Initialize the server. * * @throws LifecycleException Init error */ public void init() throws LifecycleException { getServer(); server.init(); } /** * Start the server. * * @throws LifecycleException Start error */ public void start() throws LifecycleException { getServer(); server.start(); } /** * Stop the server. * * @throws LifecycleException Stop error */ public void stop() throws LifecycleException { getServer(); server.stop(); } /** * Destroy the server. This object cannot be used once this method has been called. * * @throws LifecycleException Destroy error */ 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(). * * @param user The user name * @param pass The password */ public void addUser(String user, String pass) { userPass.put(user, pass); } /** * Add a role to a user. * * @see #addUser(String, String) * * @param user The user name * @param role The role name */ public void addRole(String user, String role) { userRoles.computeIfAbsent(user, k -> new ArrayList<>()).add(role); } // ------- Extra customization ------- // You can tune individual Tomcat objects, using internal APIs /** * Get the default HTTP connector that is used by the embedded Tomcat. It is first configured connector in the * service. If there's no connector defined, it will create and add a default connector using the port and address * specified in this Tomcat instance, and return it for further customization. * * @return The connector object */ public Connector getConnector() { Service service = getService(); if (service.findConnectors().length > 0) { return service.findConnectors()[0]; } // The same as in standard Tomcat configuration. // This creates a NIO HTTP connector. Connector connector = new Connector("HTTP/1.1"); connector.setPort(port); service.addConnector(connector); return connector; } /** * Set the specified connector in the service, if it is not already present. * * @param connector The connector instance to add */ public void setConnector(Connector connector) { Service service = getService(); boolean found = false; for (Connector serviceConnector : service.findConnectors()) { if (connector == serviceConnector) { found = true; break; } } if (!found) { service.addConnector(connector); } } /** * Get the service object. Can be used to add more connectors and few other global settings. * * @return The service */ public Service getService() { return getServer().findServices()[0]; } /** * 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 The current host */ public void setHost(Host host) { Engine engine = getEngine(); boolean found = false; for (Container engineHost : engine.findChildren()) { if (engineHost == host) { found = true; break; } } if (!found) { engine.addChild(host); } } public Host getHost() { Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); getEngine().addChild(host); return host; } /** * Access to the engine, for further customization. * * @return The engine */ public Engine getEngine() { Service service = getServer().findServices()[0]; if (service.getContainer() != null) { return service.getContainer(); } Engine engine = new StandardEngine(); engine.setName("Tomcat"); engine.setDefaultHost(hostname); engine.setRealm(createDefaultRealm()); service.setContainer(engine); return engine; } /** * Get the server object. You can add listeners and few more customizations. JNDI is disabled by default. * * @return The Server */ public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); server = new StandardServer(); initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort(-1); Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } /** * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. * @param dir Base directory for the context, for static files. Must exist, relative to the server home * * @return the deployed context * * @see #addContext(String, String) */ public Context addContext(Host host, String contextPath, String dir) { return addContext(host, contextPath, contextPath, dir); } /** * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. * @param contextName The context name * @param dir Base directory for the context, for static files. Must exist, relative to the server home * * @return the deployed context * * @see #addContext(String, String) */ public Context addContext(Host host, String contextPath, String contextName, String dir) { silence(host, contextName); Context ctx = createContext(host, contextPath); ctx.setName(contextName); ctx.setPath(contextPath); ctx.setDocBase(dir); ctx.addLifecycleListener(new FixContextListener()); if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; } /** * This is equivalent to adding a web application to a Host's appBase (usually Tomcat's webapps directory). By * default, the equivalent of the default web.xml will be applied to the web application (see * {@link #initWebappDefaults(String)}). This may be prevented by calling * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any WEB-INF/web.xml and * META-INF/context.xml packaged with the application will always be processed and normal web fragment * and {@link jakarta.servlet.ServletContainerInitializer} processing will always be applied. * * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. * @param docBase Base directory for the context, for static files. Must exist and be an absolute path. * * @return the deployed context */ public Context addWebapp(Host host, String contextPath, String docBase) { LifecycleListener listener = null; try { Class clazz = Class.forName(getHost().getConfigClass()); listener = (LifecycleListener) clazz.getConstructor().newInstance(); } catch (ReflectiveOperationException e) { // Wrap in IAE since we can't easily change the method signature to // to throw the specific checked exceptions throw new IllegalArgumentException(e); } return addWebapp(host, contextPath, docBase, listener); } /** * This is equivalent to adding a web application to a Host's appBase (usually Tomcat's webapps directory). By * default, the equivalent of the default web.xml will be applied to the web application (see * {@link #initWebappDefaults(String)}). This may be prevented by calling * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any WEB-INF/web.xml and * META-INF/context.xml packaged with the application will always be processed and normal web fragment * and {@link jakarta.servlet.ServletContainerInitializer} processing will always be applied. * * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. * @param docBase Base directory for the context, for static files. Must exist and be an absolute path. * @param config Custom context configuration helper. Any configuration will be in addition to equivalent of * the default web.xml configuration described above. * * @return the deployed context */ public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { silence(host, contextPath); Context ctx = createContext(host, contextPath); ctx.setPath(contextPath); ctx.setDocBase(docBase); if (addDefaultWebXmlToWebapp) { ctx.addLifecycleListener(getDefaultWebXmlListener()); } ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); ctx.addLifecycleListener(config); if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) { // prevent it from looking ( if it finds one - it'll have dup error ) ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath()); } if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; } /** * Return a listener that provides the required configuration items for JSP processing. From the standard Tomcat * global web.xml. Pass this to {@link Context#addLifecycleListener(LifecycleListener)} and then pass the result of * {@link #noDefaultWebXmlPath()} to {@link ContextConfig#setDefaultWebXml(String)}. * * @return a listener object that configures default JSP processing. */ public LifecycleListener getDefaultWebXmlListener() { return new DefaultWebXmlListener(); } /** * @return a pathname to pass to {@link ContextConfig#setDefaultWebXml(String)} when using * {@link #getDefaultWebXmlListener()}. */ public String noDefaultWebXmlPath() { return Constants.NoDefaultWebXml; } // ---------- Helper methods and classes ------------------- /** * Create an in-memory realm. You can replace it for contexts with a real one. The Realm created here will be added * to the Engine by default and may be replaced at the Engine level or over-ridden (as per normal Tomcat behaviour) * at the Host or Context level. * * @return a realm instance */ protected Realm createDefaultRealm() { return new SimpleRealm(); } private class SimpleRealm extends RealmBase { @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, userRoles.get(username)); userPrincipals.put(username, p); } } return p; } } protected void initBaseDir() { String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP); if (basedir == null) { basedir = System.getProperty(Globals.CATALINA_BASE_PROP); } if (basedir == null) { basedir = catalinaHome; } if (basedir == null) { // Create a temp dir. basedir = System.getProperty("user.dir") + "/tomcat." + port; } File baseFile = new File(basedir); if (baseFile.exists()) { if (!baseFile.isDirectory()) { throw new IllegalArgumentException(sm.getString("tomcat.baseDirNotDir", baseFile)); } } else { if (!baseFile.mkdirs()) { // Failed to create base directory throw new IllegalStateException(sm.getString("tomcat.baseDirMakeFail", baseFile)); } /* * If file permissions were going to be set on the newly created directory, this is the place to do it. * However, even simple calls such as File.setReadable(boolean,boolean) behaves differently on different * platforms. Therefore, setBaseDir documents that the user needs to do this. */ } try { baseFile = baseFile.getCanonicalFile(); } catch (IOException e) { baseFile = baseFile.getAbsoluteFile(); } server.setCatalinaBase(baseFile); System.setProperty(Globals.CATALINA_BASE_PROP, baseFile.getPath()); basedir = baseFile.getPath(); if (catalinaHome == null) { server.setCatalinaHome(baseFile); } else { File homeFile = new File(catalinaHome); if (!homeFile.isDirectory() && !homeFile.mkdirs()) { // Failed to create home directory throw new IllegalStateException(sm.getString("tomcat.homeDirMakeFail", homeFile)); } try { homeFile = homeFile.getCanonicalFile(); } catch (IOException e) { homeFile = homeFile.getAbsoluteFile(); } server.setCatalinaHome(homeFile); } System.setProperty(Globals.CATALINA_HOME_PROP, server.getCatalinaHome().getPath()); } static final String[] silences = new String[] { "org.apache.coyote.http11.Http11NioProtocol", "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", "org.apache.catalina.core.OpenSSLLifecycleListener" }; private boolean silent = false; /** * Controls if the loggers will be silenced or not. * * @param silent true sets the log level to WARN for the loggers that log information on Tomcat start * up. This prevents the usual startup information being logged. false sets the log * level to the default level of INFO. */ public void setSilent(boolean silent) { this.silent = silent; for (String s : silences) { Logger logger = Logger.getLogger(s); pinnedLoggers.put(s, logger); if (silent) { logger.setLevel(Level.WARNING); } else { logger.setLevel(Level.INFO); } } } private void silence(Host host, String contextPath) { String loggerName = getLoggerName(host, contextPath); Logger logger = Logger.getLogger(loggerName); pinnedLoggers.put(loggerName, logger); if (silent) { logger.setLevel(Level.WARNING); } else { logger.setLevel(Level.INFO); } } /** * By default, when calling addWebapp() to create a Context, the settings from from the default web.xml are added to * the context. Calling this method with a false value prior to calling addWebapp() allows to opt out * of the default settings. In that event you will need to add the configurations yourself, either programmatically * or by using web.xml deployment descriptors. * * @param addDefaultWebXmlToWebapp false will prevent the class from automatically adding the default * settings when calling addWebapp(). true will add the default * settings and is the default behavior. * * @see #addWebapp(Host, String, String, LifecycleListener) */ public void setAddDefaultWebXmlToWebapp(boolean addDefaultWebXmlToWebapp) { this.addDefaultWebXmlToWebapp = addDefaultWebXmlToWebapp; } /* * Uses essentially the same logic as {@link ContainerBase#logName()}. */ private String getLoggerName(Host host, String contextName) { if (host == null) { host = getHost(); } StringBuilder loggerName = new StringBuilder(); loggerName.append(ContainerBase.class.getName()); loggerName.append(".["); // Engine name loggerName.append(host.getParent().getName()); loggerName.append("].["); // Host name loggerName.append(host.getName()); loggerName.append("].["); // Context name if (contextName == null || contextName.equals("")) { loggerName.append('/'); } else if (contextName.startsWith("##")) { loggerName.append('/'); loggerName.append(contextName); } loggerName.append(']'); return loggerName.toString(); } /** * Create the configured {@link Context} for the given host. The default constructor of the class that * was configured with {@link StandardHost#setContextClass(String)} will be used * * @param host host for which the {@link Context} should be created, or null if default host should be * used * @param url path of the webapp which should get the {@link Context} * * @return newly created {@link Context} */ private Context createContext(Host host, String url) { String defaultContextClass = StandardContext.class.getName(); String contextClass = StandardContext.class.getName(); if (host == null) { host = this.getHost(); } if (host instanceof StandardHost) { contextClass = ((StandardHost) host).getContextClass(); } try { if (defaultContextClass.equals(contextClass)) { return new StandardContext(); } else { return (Context) Class.forName(contextClass).getConstructor().newInstance(); } } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) { throw new IllegalArgumentException(sm.getString("tomcat.noContextClass", contextClass, host, url), e); } } /** * 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 broadly the programmatic equivalent of the default web.xml * and provides the following features: *
    *
  • Default servlet mapped to "/"
  • *
  • JSP servlet mapped to "*.jsp" and ""*.jspx"
  • *
  • Session timeout of 30 minutes
  • *
  • MIME mappings (subset of those in conf/web.xml)
  • *
  • Welcome files
  • *
* TODO: Align the MIME mappings with conf/web.xml - possibly via a common file. * * @param contextPath The path of 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); servlet.setOverridable(true); // 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.setOverridable(true); // Servlet mappings ctx.addServletMappingDecoded("/", "default"); ctx.addServletMappingDecoded("*.jsp", "jsp"); ctx.addServletMappingDecoded("*.jspx", "jsp"); // Sessions ctx.setSessionTimeout(30); // MIME type mappings addDefaultMimeTypeMappings(ctx); // Welcome files ctx.addWelcomeFile("index.html"); ctx.addWelcomeFile("index.htm"); ctx.addWelcomeFile("index.jsp"); } /** * Add the default MIME type mappings to the provide Context. * * @param context The web application to which the default MIME type mappings should be added. */ public static void addDefaultMimeTypeMappings(Context context) { Properties defaultMimeMappings = new Properties(); try (InputStream is = Tomcat.class.getResourceAsStream("MimeTypeMappings.properties")) { defaultMimeMappings.load(is); for (Map.Entry entry : defaultMimeMappings.entrySet()) { context.addMimeMapping((String) entry.getKey(), (String) entry.getValue()); } } catch (IOException e) { throw new IllegalStateException(sm.getString("tomcat.defaultMimeTypeMappingsFail"), e); } } /** * 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); // Process annotations WebAnnotationSet.loadApplicationAnnotations(context); // 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) { } } } /** * 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 final Servlet existing; public ExistingStandardWrapper(Servlet existing) { this.existing = existing; this.asyncSupported = hasAsync(existing); } private static boolean hasAsync(Servlet existing) { boolean result = false; Class clazz = existing.getClass(); WebServlet ws = clazz.getAnnotation(WebServlet.class); if (ws != null) { result = ws.asyncSupported(); } return result; } @Override public synchronized Servlet loadServlet() throws ServletException { if (!instanceInitialized) { existing.init(facade); instanceInitialized = 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(); } } protected URL getWebappConfigFile(String path, String contextName) { File docBase = new File(path); if (docBase.isDirectory()) { return getWebappConfigFileFromDirectory(docBase, contextName); } else { return getWebappConfigFileFromWar(docBase, contextName); } } private URL getWebappConfigFileFromDirectory(File docBase, String contextName) { URL result = null; File webAppContextXml = new File(docBase, Constants.ApplicationContextXml); if (webAppContextXml.exists()) { try { result = webAppContextXml.toURI().toURL(); } catch (MalformedURLException e) { Logger.getLogger(getLoggerName(getHost(), contextName)).log(Level.WARNING, sm.getString("tomcat.noContextXml", docBase), e); } } return result; } private URL getWebappConfigFileFromWar(File docBase, String contextName) { URL result = null; try (JarFile jar = new JarFile(docBase)) { JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml); if (entry != null) { result = UriUtil.buildJarUrl(docBase, Constants.ApplicationContextXml); } } catch (IOException e) { Logger.getLogger(getLoggerName(getHost(), contextName)).log(Level.WARNING, sm.getString("tomcat.noContextXml", docBase), e); } return result; } static { // Graal native images don't load any configuration except the VM default if (JreCompat.isGraalAvailable()) { try (InputStream is = new FileInputStream( new File(System.getProperty("java.util.logging.config.file", "conf/logging.properties")))) { LogManager.getLogManager().readConfiguration(is); } catch (SecurityException | IOException e) { // Ignore, the VM default will be used } } } /** * Main executable method for use with a Maven packager. * * @param args the command line arguments * * @throws Exception if an error occurs */ public static void main(String[] args) throws Exception { // Process some command line parameters String[] catalinaArguments = null; for (int i = 0; i < args.length; i++) { if (args[i].equals("--no-jmx")) { Registry.disableRegistry(); } else if (args[i].equals("--catalina")) { // This was already processed before // Skip the rest of the arguments as they are for Catalina ArrayList result = new ArrayList<>(); for (int j = i + 1; j < args.length; j++) { result.add(args[j]); } catalinaArguments = result.toArray(new String[0]); break; } } Tomcat tomcat = new Tomcat(); // Create a Catalina instance and let it parse the configuration files // It will also set a shutdown hook to stop the Server when needed // Use the default configuration source tomcat.init(null, catalinaArguments); boolean await = false; String path = ""; // Process command line parameters for (int i = 0; i < args.length; i++) { if (args[i].equals("--war")) { if (++i >= args.length) { throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i - 1])); } File war = new File(args[i]); tomcat.addWebapp(path, war.getAbsolutePath()); } else if (args[i].equals("--path")) { if (++i >= args.length) { throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i - 1])); } path = args[i]; } else if (args[i].equals("--await")) { await = true; } else if (args[i].equals("--no-jmx")) { // This was already processed before } else if (args[i].equals("--catalina")) { // This was already processed before // Skip the rest of the arguments as they are for Catalina break; } else { throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i])); } } tomcat.start(); // Ideally the utility threads are non daemon if (await) { tomcat.getServer().await(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy