/* * 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.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.net.ConnectException; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.logging.LogManager; import org.apache.catalina.Container; import org.apache.catalina.Globals; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Server; import org.apache.catalina.security.SecurityConfig; import org.apache.juli.ClassLoaderLogManager; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.digester.Rule; import org.apache.tomcat.util.digester.RuleSet; import org.apache.tomcat.util.log.SystemLogHandler; import org.apache.tomcat.util.res.StringManager; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; /** * Startup/Shutdown shell program for Catalina. The following command line * options are recognized: * <ul> * <li><b>-config {pathname}</b> - Set the pathname of the configuration file * to be processed. If a relative path is specified, it will be * interpreted as relative to the directory pathname specified by the * "catalina.base" system property. [conf/server.xml] * <li><b>-help</b> - Display usage information. * <li><b>-nonaming</b> - Disable naming support. * <li><b>configtest</b> - Try to test the config * <li><b>start</b> - Start an instance of Catalina. * <li><b>stop</b> - Stop the currently running instance of Catalina. * </u> * * Should do the same thing as Embedded, but using a server.xml file. * * @author Craig R. McClanahan * @author Remy Maucherat */ public class Catalina { /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); // ----------------------------------------------------- Instance Variables /** * Use await. */ protected boolean await = false; /** * Pathname to the server configuration file. */ protected String configFile = "conf/server.xml"; // XXX Should be moved to embedded /** * The shared extensions class loader for this server. */ protected ClassLoader parentClassLoader = Catalina.class.getClassLoader(); /** * The server component we are starting or stopping. */ protected Server server = null; /** * Are we starting a new server? * * @deprecated Unused - will be removed in Tomcat 8.0.x */ @Deprecated protected boolean starting = false; /** * Are we stopping an existing server? * * @deprecated Unused - will be removed in Tomcat 8.0.x */ @Deprecated protected boolean stopping = false; /** * Use shutdown hook flag. */ protected boolean useShutdownHook = true; /** * Shutdown hook. */ protected Thread shutdownHook = null; /** * Is naming enabled ? */ protected boolean useNaming = true; // ----------------------------------------------------------- Constructors public Catalina() { setSecurityProtection(); ExceptionUtils.preload(); } // ------------------------------------------------------------- Properties /** * @deprecated Use {@link #setConfigFile(String)} */ @Deprecated public void setConfig(String file) { configFile = file; } public void setConfigFile(String file) { configFile = file; } public String getConfigFile() { return configFile; } public void setUseShutdownHook(boolean useShutdownHook) { this.useShutdownHook = useShutdownHook; } public boolean getUseShutdownHook() { return useShutdownHook; } /** * Set the shared extensions class loader. * * @param parentClassLoader The shared extensions class loader. */ public void setParentClassLoader(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; } public ClassLoader getParentClassLoader() { if (parentClassLoader != null) { return (parentClassLoader); } return ClassLoader.getSystemClassLoader(); } public void setServer(Server server) { this.server = server; } public Server getServer() { return server; } /** * Return true if naming is enabled. */ public boolean isUseNaming() { return (this.useNaming); } /** * Enables or disables naming support. * * @param useNaming The new use naming value */ public void setUseNaming(boolean useNaming) { this.useNaming = useNaming; } public void setAwait(boolean b) { await = b; } public boolean isAwait() { return await; } // ------------------------------------------------------ Protected Methods /** * Process the specified command line arguments, and return * <code>true</code> if we should continue processing; otherwise * return <code>false</code>. * * @param args Command line arguments to process */ protected boolean arguments(String args[]) { boolean isConfig = false; if (args.length < 1) { usage(); return (false); } for (int i = 0; i < args.length; i++) { if (isConfig) { configFile = args[i]; isConfig = false; } else if (args[i].equals("-config")) { isConfig = true; } else if (args[i].equals("-nonaming")) { setUseNaming( false ); } else if (args[i].equals("-help")) { usage(); return (false); } else if (args[i].equals("start")) { starting = true; stopping = false; } else if (args[i].equals("configtest")) { starting = true; stopping = false; } else if (args[i].equals("stop")) { starting = false; stopping = true; } else { usage(); return (false); } } return (true); } /** * Return a File object representing our configuration file. */ protected File configFile() { File file = new File(configFile); if (!file.isAbsolute()) { file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile); } return (file); } /** * Create and configure the Digester we will be using for startup. */ protected Digester createStartDigester() { long t1=System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<Class<?>, List<String>>(); ArrayList<String> attrs = new ArrayList<String>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setUseContextClassLoader(true); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); //Executor digester.addObjectCreate("Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor"})); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/"); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); long t2=System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Digester for server.xml created " + ( t2-t1 )); } return (digester); } /** * Cluster support is optional. The JARs may have been removed. */ private void addClusterRuleSet(Digester digester, String prefix) { Class<?> clazz = null; Constructor<?> constructor = null; try { clazz = Class.forName("org.apache.catalina.ha.ClusterRuleSet"); constructor = clazz.getConstructor(String.class); RuleSet ruleSet = (RuleSet) constructor.newInstance(prefix); digester.addRuleSet(ruleSet); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.noCluster", e.getClass().getName() + ": " + e.getMessage()), e); } else if (log.isInfoEnabled()) { log.info(sm.getString("catalina.noCluster", e.getClass().getName() + ": " + e.getMessage())); } } } /** * Create and configure the Digester we will be using for shutdown. */ protected Digester createStopDigester() { // Initialize the digester Digester digester = new Digester(); digester.setUseContextClassLoader(true); // Configure the rules we need for shutting down digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); return (digester); } public void stopServer() { stopServer(null); } public void stopServer(String[] arguments) { if (arguments != null) { arguments(arguments); } Server s = getServer(); if( s == null ) { // Create and execute our Digester Digester digester = createStopDigester(); File file = configFile(); FileInputStream fis = null; try { InputSource is = new InputSource(file.toURI().toURL().toString()); fis = new FileInputStream(file); is.setByteStream(fis); digester.push(this); digester.parse(is); } catch (Exception e) { log.error("Catalina.stop: ", e); System.exit(1); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // Ignore } } } } else { // Server object already present. Must be running as a service try { s.stop(); } catch (LifecycleException e) { log.error("Catalina.stop: ", e); } return; } // Stop the existing server s = getServer(); if (s.getPort()>0) { Socket socket = null; OutputStream stream = null; try { socket = new Socket(s.getAddress(), s.getPort()); stream = socket.getOutputStream(); String shutdown = s.getShutdown(); for (int i = 0; i < shutdown.length(); i++) { stream.write(shutdown.charAt(i)); } stream.flush(); } catch (ConnectException ce) { log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(), String.valueOf(s.getPort()))); log.error("Catalina.stop: ", ce); System.exit(1); } catch (IOException e) { log.error("Catalina.stop: ", e); System.exit(1); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // Ignore } } if (socket != null) { try { socket.close(); } catch (IOException e) { // Ignore } } } } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } } /** * Start a new server instance. */ public void load() { long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { try { file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource(file.toURI().toURL().toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", file), e); } } if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream(getConfigFile()); inputSource = new InputSource (getClass().getClassLoader() .getResource(getConfigFile()).toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", getConfigFile()), e); } } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if( inputStream==null ) { try { inputStream = getClass().getClassLoader() .getResourceAsStream("server-embed.xml"); inputSource = new InputSource (getClass().getClassLoader() .getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } try { inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } getServer().setCatalina(this); // Stream redirection initStreams(); // Start the new server try { getServer().init(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { throw new java.lang.Error(e); } else { log.error("Catalina.start", e); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } } /* * Load using arguments */ public void load(String args[]) { try { if (arguments(args)) { load(); } } catch (Exception e) { e.printStackTrace(System.out); } } /** * Start a new server instance. */ public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server try { getServer().start(); } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } if (await) { await(); stop(); } } /** * Stop an existing server instance. */ public void stop() { try { // Remove the ShutdownHook first so that server.stop() // doesn't get invoked twice if (useShutdownHook) { Runtime.getRuntime().removeShutdownHook(shutdownHook); // If JULI is being used, re-enable JULI's shutdown to ensure // log messages are not lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( true); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } // Shut down the server try { Server s = getServer(); LifecycleState state = s.getState(); if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0 && LifecycleState.DESTROYED.compareTo(state) >= 0) { // Nothing to do. stop() was already called } else { s.stop(); s.destroy(); } } catch (LifecycleException e) { log.error("Catalina.stop", e); } } /** * Await and shutdown. */ public void await() { getServer().await(); } /** * Print usage information for this application. */ protected void usage() { System.out.println ("usage: java org.apache.catalina.startup.Catalina" + " [ -config {pathname} ]" + " [ -nonaming ] " + " { -help | start | stop }"); } protected void initDirs() { String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP); if (catalinaHome == null) { // Backwards compatibility patch for J2EE RI 1.3 String j2eeHome = System.getProperty("com.sun.enterprise.home"); if (j2eeHome != null) { catalinaHome=System.getProperty("com.sun.enterprise.home"); } else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) { catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP); } } // last resort - for minimal/embedded cases. if(catalinaHome==null) { catalinaHome=System.getProperty("user.dir"); } if (catalinaHome != null) { File home = new File(catalinaHome); if (!home.isAbsolute()) { try { catalinaHome = home.getCanonicalPath(); } catch (IOException e) { catalinaHome = home.getAbsolutePath(); } } System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome); } if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) { System.setProperty(Globals.CATALINA_BASE_PROP, catalinaHome); } else { String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP); File base = new File(catalinaBase); if (!base.isAbsolute()) { try { catalinaBase = base.getCanonicalPath(); } catch (IOException e) { catalinaBase = base.getAbsolutePath(); } } System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase); } String temp = System.getProperty("java.io.tmpdir"); if (temp == null || (!(new File(temp)).exists()) || (!(new File(temp)).isDirectory())) { log.error(sm.getString("embedded.notmp", temp)); } } protected void initStreams() { // Replace System.out and System.err with a custom PrintStream System.setOut(new SystemLogHandler(System.out)); System.setErr(new SystemLogHandler(System.err)); } protected void initNaming() { // Setting additional variables if (!useNaming) { log.info( "Catalina naming disabled"); System.setProperty("catalina.useNaming", "false"); } else { System.setProperty("catalina.useNaming", "true"); String value = "org.apache.naming"; String oldValue = System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); if (oldValue != null) { value = value + ":" + oldValue; } System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); if( log.isDebugEnabled() ) { log.debug("Setting naming prefix=" + 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"); } else { log.debug( "INITIAL_CONTEXT_FACTORY already set " + value ); } } } /** * Set the security package access/protection. */ protected void setSecurityProtection(){ SecurityConfig securityConfig = SecurityConfig.newInstance(); securityConfig.setPackageDefinition(); securityConfig.setPackageAccess(); } // --------------------------------------- CatalinaShutdownHook Inner Class // XXX Should be moved to embedded ! /** * Shutdown hook which will perform a clean shutdown of Catalina if needed. */ protected class CatalinaShutdownHook extends Thread { @Override public void run() { try { if (getServer() != null) { Catalina.this.stop(); } } catch (Throwable ex) { ExceptionUtils.handleThrowable(ex); log.error(sm.getString("catalina.shutdownHookFail"), ex); } finally { // If JULI is used, shut JULI down *after* the server shuts down // so log messages aren't lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).shutdown(); } } } } private static final org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( Catalina.class ); } // ------------------------------------------------------------ Private Classes /** * Rule that sets the parent class loader for the top object on the stack, * which must be a <code>Container</code>. */ final class SetParentClassLoaderRule extends Rule { public SetParentClassLoaderRule(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; } ClassLoader parentClassLoader = null; @Override public void begin(String namespace, String name, Attributes attributes) throws Exception { if (digester.getLogger().isDebugEnabled()) { digester.getLogger().debug("Setting parent class loader"); } Container top = (Container) digester.peek(); top.setParentClassLoader(parentClassLoader); } }