/* * 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.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import org.apache.catalina.Realm; import org.apache.catalina.connector.Connector; 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 TomcatStartupAPITest for examples on how to use this * @author Costin Manolache */ public class Tomcat { // Single engine, service, server, connector - few cases need more, // they can use server.xml protected StandardServer server; protected StandardService service; protected StandardEngine 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 StandardHost 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 userPass = new HashMap(); private Map> userRoles = new HashMap>(); private Map userPrincipals = new HashMap(); public Tomcat() { } /** * 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 * @throws ServletException */ public StandardContext 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"); * * TODO: add the rest * * @param host NULL for the 'default' host * @param contextPath "/" for root context. * @param dir base dir for the context, for static files. Must exist, * relative to the server home */ public StandardContext addContext(String contextPath, String baseDir) { return addContext(getHost(), contextPath, baseDir); } public StandardWrapper addServlet(String contextPath, String servletName, String servletClass) { Container ctx = getHost().findChild(contextPath); return addServlet((StandardContext) ctx, servletName, servletClass); } /** * Equivalent with * . * * 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"); */ public StandardWrapper addServlet(StandardContext ctx, String servletName, String servletClass) { // will do class for name and set init params StandardWrapper sw = (StandardWrapper)ctx.createWrapper(); sw.setServletClass(servletClass); sw.setName(servletName); ctx.addChild(sw); return sw; } /** Use an existing servlet, no class.forName or initialization will be * performed */ public StandardWrapper addServlet(StandardContext ctx, String servletName, Servlet servlet) { // will do class for name and set init params StandardWrapper sw = new ExistingStandardWrapper(servlet); sw.setName(servletName); ctx.addChild(sw); return sw; } /** * Initialize and start the server. */ public void start() throws Exception { setSilent(); getServer(); getConnector(); server.initialize(); server.start(); } /** * Stop the server. */ public void stop() throws Exception { getServer().stop(); } /** * 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 */ public void addRole(String user, String role) { List roles = userRoles.get(user); if (roles == null) { roles = new ArrayList(); 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() throws Exception { getServer(); if (connector != null) { return connector; } // This will load Apr connector if available, // default to nio. I'm having strange problems with apr // 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 StandardService 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(StandardHost host) { this.host = host; } public StandardHost 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 StandardEngine getEngine() { if(engine == null ) { getServer(); engine = new StandardEngine(); engine.setName( "default" ); engine.setDefaultHost(hostname); service.setContainer(engine); } return engine; } /** * Get the server object. You can add listeners and * few more customizations. */ public StandardServer 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 StandardContext addContext(StandardHost host, String contextPath, String dir) { silence(contextPath); StandardContext ctx = new StandardContext(); ctx.setPath( contextPath ); ctx.setDocBase(dir); ctx.addLifecycleListener(new FixContextListener()); if (host == null) { host = getHost(); } host.addChild(ctx); return ctx; } public StandardContext addWebapp(StandardHost host, String url, String path) throws ServletException { silence(url); StandardContext ctx = new StandardContext(); ctx.setPath( url ); ctx.setDocBase(path); if (defaultRealm == null) { initSimpleAuth(); } ctx.setRealm(defaultRealm); initWebappDefaults(ctx); 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) { host = getHost(); } 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(this, username, pass, userRoles.get(username)); userPrincipals.put(username, p); } } return p; } }; } protected void initBaseDir() { if (basedir == null) { basedir = System.getProperty("catalina.base"); } if (basedir == null) { basedir = System.getProperty("catalina.home"); } 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("catalina.home", basedir); System.setProperty("catalina.base", 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", }; public void setSilent() { for (String s : silences) { Logger.getLogger(s).setLevel(Level.WARNING); } } 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); } /** Init default servlets for the context. This should be the programmatic * equivalent of the default web.xml. * * TODO: in normal tomcat, if default-web.xml is not found, use this * method */ protected void initWebappDefaults(StandardContext ctx) { // Default servlet StandardWrapper servlet = addServlet(ctx, "default", //new DefaultServlet()); // Or: "org.apache.catalina.servlets.DefaultServlet"); servlet.addInitParameter("listings", "false"); servlet.setLoadOnStartup(1); // class name - to avoid loading all deps servlet = addServlet(ctx, "jsp", "org.apache.jasper.servlet.JspServlet"); servlet.addInitParameter("fork", "false"); servlet.addInitParameter("xpoweredBy", "false"); // in default web.xml - but not here, only needed if you have // jsps. //servlet.setLoadOnStartup(3); ctx.addServletMapping("/", "default"); ctx.addServletMapping("*.jsp", "jsp"); ctx.addServletMapping("*.jspx", "jsp"); // Sessions ctx.setManager( new StandardManager()); ctx.setSessionTimeout(30); // TODO: read mime from /etc/mime.types on linux, or some // resource for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length; ) { ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++], DEFAULT_MIME_MAPPINGS[i++]); } ctx.addWelcomeFile("index.html"); ctx.addWelcomeFile("index.htm"); ctx.addWelcomeFile("index.jsp"); ctx.setLoginConfig( new LoginConfig("NONE", null, null, null)); // TODO: set a default realm, add simple API to add users } /** 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 { public void lifecycleEvent(LifecycleEvent event) { try { Context context = (Context) event.getLifecycle(); if (event.getType().equals(Lifecycle.START_EVENT)) { context.setConfigured(true); } } catch (ClassCastException e) { return; } } } /** 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; } public synchronized Servlet loadServlet() throws ServletException { if (!init) { existing.init(facade); init = true; } return existing; } public long getAvailable() { return 0; } 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. */ public 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", "avi", "video/x-msvideo", "avx", "video/x-rad-screenplay", "bcpio", "application/x-bcpio", "bin", "application/octet-stream", "bmp", "image/bmp", "body", "text/html", "cdf", "application/x-cdf", "cer", "application/x-x509-ca-cert", "class", "application/java", "cpio", "application/x-cpio", "csh", "application/x-csh", "css", "text/css", "dib", "image/bmp", "doc", "application/msword", "dtd", "application/xml-dtd", "dv", "video/x-dv", "dvi", "application/x-dvi", "eps", "application/postscript", "etx", "text/x-setext", "exe", "application/octet-stream", "gif", "image/gif", "gtar", "application/x-gtar", "gz", "application/x-gzip", "hdf", "application/x-hdf", "hqx", "application/mac-binhex40", "htc", "text/x-component", "htm", "text/html", "html", "text/html", "hqx", "application/mac-binhex40", "ief", "image/ief", "jad", "text/vnd.sun.j2me.app-descriptor", "jar", "application/java-archive", "java", "text/plain", "jnlp", "application/x-java-jnlp-file", "jpe", "image/jpeg", "jpeg", "image/jpeg", "jpg", "image/jpeg", "js", "text/javascript", "jsf", "text/plain", "jspf", "text/plain", "kar", "audio/x-midi", "latex", "application/x-latex", "m3u", "audio/x-mpegurl", "mac", "image/x-macpaint", "man", "application/x-troff-man", "mathml", "application/mathml+xml", "me", "application/x-troff-me", "mid", "audio/x-midi", "midi", "audio/x-midi", "mif", "application/x-mif", "mov", "video/quicktime", "movie", "video/x-sgi-movie", "mp1", "audio/x-mpeg", "mp2", "audio/x-mpeg", "mp3", "audio/x-mpeg", "mp4", "video/mp4", "mpa", "audio/x-mpeg", "mpe", "video/mpeg", "mpeg", "video/mpeg", "mpega", "audio/x-mpeg", "mpg", "video/mpeg", "mpv2", "video/mpeg2", "ms", "application/x-wais-source", "nc", "application/x-netcdf", "oda", "application/oda", "odb", "application/vnd.oasis.opendocument.database", "odc", "application/vnd.oasis.opendocument.chart", "odf", "application/vnd.oasis.opendocument.formula", "odg", "application/vnd.oasis.opendocument.graphics", "odi", "application/vnd.oasis.opendocument.image", "odm", "application/vnd.oasis.opendocument.text-master", "odp", "application/vnd.oasis.opendocument.presentation", "ods", "application/vnd.oasis.opendocument.spreadsheet", "odt", "application/vnd.oasis.opendocument.text", "ogg", "application/ogg", "otg ", "application/vnd.oasis.opendocument.graphics-template", "oth", "application/vnd.oasis.opendocument.text-web", "otp", "application/vnd.oasis.opendocument.presentation-template", "ots", "application/vnd.oasis.opendocument.spreadsheet-template ", "ott", "application/vnd.oasis.opendocument.text-template", "pbm", "image/x-portable-bitmap", "pct", "image/pict", "pdf", "application/pdf", "pgm", "image/x-portable-graymap", "pic", "image/pict", "pict", "image/pict", "pls", "audio/x-scpls", "png", "image/png", "pnm", "image/x-portable-anymap", "pnt", "image/x-macpaint", "ppm", "image/x-portable-pixmap", "ppt", "application/powerpoint", "ps", "application/postscript", "psd", "image/x-photoshop", "qt", "video/quicktime", "qti", "image/x-quicktime", "qtif", "image/x-quicktime", "ras", "image/x-cmu-raster", "rdf", "application/rdf+xml", "rgb", "image/x-rgb", "rm", "application/vnd.rn-realmedia", "roff", "application/x-troff", "rtf", "application/rtf", "rtx", "text/richtext", "sh", "application/x-sh", "shar", "application/x-shar", /*"shtml", "text/x-server-parsed-html",*/ "smf", "audio/x-midi", "sit", "application/x-stuffit", "snd", "audio/basic", "src", "application/x-wais-source", "sv4cpio", "application/x-sv4cpio", "sv4crc", "application/x-sv4crc", "swf", "application/x-shockwave-flash", "t", "application/x-troff", "tar", "application/x-tar", "tcl", "application/x-tcl", "tex", "application/x-tex", "texi", "application/x-texinfo", "texinfo", "application/x-texinfo", "tif", "image/tiff", "tiff", "image/tiff", "tr", "application/x-troff", "tsv", "text/tab-separated-values", "txt", "text/plain", "ulw", "audio/basic", "ustar", "application/x-ustar", "vxml", "application/voicexml+xml", "xbm", "image/x-xbitmap", "xht", "application/xhtml+xml", "xhtml", "application/xhtml+xml", "xml", "application/xml", "xpm", "image/x-xpixmap", "xsl", "application/xml", "xslt", "application/xslt+xml", "xul", "application/vnd.mozilla.xul+xml", "xwd", "image/x-xwindowdump", "wav", "audio/x-wav", "svg", "image/svg+xml", "svgz", "image/svg+xml", "vsd", "application/x-visio", "wbmp", "image/vnd.wap.wbmp", "wml", "text/vnd.wap.wml", "wmlc", "application/vnd.wap.wmlc", "wmls", "text/vnd.wap.wmlscript", "wmlscriptc", "application/vnd.wap.wmlscriptc", "wmv", "video/x-ms-wmv", "wrl", "x-world/x-vrml", "wspolicy", "application/wspolicy+xml", "Z", "application/x-compress", "z", "application/x-compress", "zip", "application/zip", "xls", "application/vnd.ms-excel", "doc", "application/vnd.ms-word", "ppt", "application/vnd.ms-powerpoint" }; }