/* * 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.core; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.regex.Pattern; import javax.management.MBeanServer; import javax.management.ObjectName; import org.apache.catalina.Container; import org.apache.catalina.Context; 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.Valve; import org.apache.catalina.loader.WebappClassLoader; import org.apache.catalina.startup.HostConfig; import org.apache.catalina.valves.ValveBase; import org.apache.tomcat.util.modeler.Registry; /** * Standard implementation of the <b>Host</b> interface. Each * child container must be a Context implementation to process the * requests directed to a particular web application. * * @author Craig R. McClanahan * @author Remy Maucherat * */ public class StandardHost extends ContainerBase implements Host { /* Why do we implement deployer and delegate to deployer ??? */ private static org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( StandardHost.class ); // ----------------------------------------------------------- Constructors /** * Create a new StandardHost component with the default basic Valve. */ public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); } // ----------------------------------------------------- Instance Variables /** * The set of aliases for this Host. */ private String[] aliases = new String[0]; private final Object aliasesLock = new Object(); /** * The application root for this Host. */ private String appBase = "webapps"; /** * The auto deploy flag for this Host. */ private boolean autoDeploy = true; /** * The Java class name of the default context configuration class * for deployed web applications. */ private String configClass = "org.apache.catalina.startup.ContextConfig"; /** * The Java class name of the default Context implementation class for * deployed web applications. */ private String contextClass = "org.apache.catalina.core.StandardContext"; /** * The deploy on startup flag for this Host. */ private boolean deployOnStartup = true; /** * deploy Context XML config files property. */ private boolean deployXML = true; /** * The Java class name of the default error reporter implementation class * for deployed web applications. */ private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve"; /** * The object name for the errorReportValve. */ private ObjectName errorReportValveObjectName = null; /** * The descriptive information string for this implementation. */ private static final String info = "org.apache.catalina.core.StandardHost/1.0"; /** * The live deploy flag for this Host. */ private boolean liveDeploy = true; /** * Unpack WARs property. */ private boolean unpackWARs = true; /** * Work Directory base for applications. */ private String workDir = null; /** * Attribute value used to turn on/off XML validation */ private boolean xmlValidation = false; /** * Attribute value used to turn on/off XML namespace awarenes. */ private boolean xmlNamespaceAware = false; /** * Track the class loaders for the child web applications so memory leaks * can be detected. */ private Map<ClassLoader, String> childClassLoaders = new WeakHashMap<ClassLoader, String>(); /** * Any file or directory in {@link #appBase} that this pattern matches will * be ignored by the automatic deployment process (both * {@link #deployOnStartup} and {@link #autoDeploy}). */ private Pattern deployIgnore = null; // ------------------------------------------------------------- Properties /** * Return the application root for this Host. This can be an absolute * pathname, a relative pathname, or a URL. */ public String getAppBase() { return (this.appBase); } /** * Set the application root for this Host. This can be an absolute * pathname, a relative pathname, or a URL. * * @param appBase The new application root */ public void setAppBase(String appBase) { String oldAppBase = this.appBase; this.appBase = appBase; support.firePropertyChange("appBase", oldAppBase, this.appBase); } /** * Return the value of the auto deploy flag. If true, it indicates that * this host's child webapps will be dynamically deployed. */ public boolean getAutoDeploy() { return (this.autoDeploy); } /** * Set the auto deploy flag value for this host. * * @param autoDeploy The new auto deploy flag */ public void setAutoDeploy(boolean autoDeploy) { boolean oldAutoDeploy = this.autoDeploy; this.autoDeploy = autoDeploy; support.firePropertyChange("autoDeploy", oldAutoDeploy, this.autoDeploy); } /** * Return the Java class name of the context configuration class * for new web applications. */ public String getConfigClass() { return (this.configClass); } /** * Set the Java class name of the context configuration class * for new web applications. * * @param configClass The new context configuration class */ public void setConfigClass(String configClass) { String oldConfigClass = this.configClass; this.configClass = configClass; support.firePropertyChange("configClass", oldConfigClass, this.configClass); } /** * Return the Java class name of the Context implementation class * for new web applications. */ public String getContextClass() { return (this.contextClass); } /** * Set the Java class name of the Context implementation class * for new web applications. * * @param contextClass The new context implementation class */ public void setContextClass(String contextClass) { String oldContextClass = this.contextClass; this.contextClass = contextClass; support.firePropertyChange("contextClass", oldContextClass, this.contextClass); } /** * Return the value of the deploy on startup flag. If true, it indicates * that this host's child webapps should be discovred and automatically * deployed at startup time. */ public boolean getDeployOnStartup() { return (this.deployOnStartup); } /** * Set the deploy on startup flag value for this host. * * @param deployOnStartup The new deploy on startup flag */ public void setDeployOnStartup(boolean deployOnStartup) { boolean oldDeployOnStartup = this.deployOnStartup; this.deployOnStartup = deployOnStartup; support.firePropertyChange("deployOnStartup", oldDeployOnStartup, this.deployOnStartup); } /** * Deploy XML Context config files flag accessor. */ public boolean isDeployXML() { return (deployXML); } /** * Deploy XML Context config files flag mutator. */ public void setDeployXML(boolean deployXML) { this.deployXML = deployXML; } /** * Return the value of the live deploy flag. If true, it indicates that * a background thread should be started that looks for web application * context files, WAR files, or unpacked directories being dropped in to * the <code>appBase</code> directory, and deploys new ones as they are * encountered. */ public boolean getLiveDeploy() { return (this.autoDeploy); } /** * Set the live deploy flag value for this host. * * @param liveDeploy The new live deploy flag */ public void setLiveDeploy(boolean liveDeploy) { setAutoDeploy(liveDeploy); } /** * Return the Java class name of the error report valve class * for new web applications. */ public String getErrorReportValveClass() { return (this.errorReportValveClass); } /** * Set the Java class name of the error report valve class * for new web applications. * * @param errorReportValveClass The new error report valve class */ public void setErrorReportValveClass(String errorReportValveClass) { String oldErrorReportValveClassClass = this.errorReportValveClass; this.errorReportValveClass = errorReportValveClass; support.firePropertyChange("errorReportValveClass", oldErrorReportValveClassClass, this.errorReportValveClass); } /** * Return the canonical, fully qualified, name of the virtual host * this Container represents. */ public String getName() { return (name); } /** * Set the canonical, fully qualified, name of the virtual host * this Container represents. * * @param name Virtual host name * * @exception IllegalArgumentException if name is null */ public void setName(String name) { if (name == null) throw new IllegalArgumentException (sm.getString("standardHost.nullName")); name = name.toLowerCase(); // Internally all names are lower case String oldName = this.name; this.name = name; support.firePropertyChange("name", oldName, this.name); } /** * Unpack WARs flag accessor. */ public boolean isUnpackWARs() { return (unpackWARs); } /** * Unpack WARs flag mutator. */ public void setUnpackWARs(boolean unpackWARs) { this.unpackWARs = unpackWARs; } public void setXmlValidation(boolean xmlValidation){ this.xmlValidation = xmlValidation; } public boolean getXmlValidation(){ return xmlValidation; } public boolean getXmlNamespaceAware(){ return xmlNamespaceAware; } public void setXmlNamespaceAware(boolean xmlNamespaceAware){ this.xmlNamespaceAware=xmlNamespaceAware; } /** * Host work directory base. */ public String getWorkDir() { return (workDir); } /** * Host work directory base. */ public void setWorkDir(String workDir) { this.workDir = workDir; } /** * Return the regular expression that defines the files and directories in * the host's {@link #appBase} that will be ignored by the automatic * deployment process. */ public String getDeployIgnore() { if (deployIgnore == null) { return null; } return this.deployIgnore.toString(); } /** * Return the compiled regular expression that defines the files and * directories in the host's {@link #appBase} that will be ignored by the * automatic deployment process. */ public Pattern getDeployIgnorePattern() { return this.deployIgnore; } /** * Set the regular expression that defines the files and directories in * the host's {@link #appBase} that will be ignored by the automatic * deployment process. */ public void setDeployIgnore(String deployIgnore) { String oldDeployIgnore; if (this.deployIgnore == null) { oldDeployIgnore = null; } else { oldDeployIgnore = this.deployIgnore.toString(); } if (deployIgnore == null) { this.deployIgnore = null; } else { this.deployIgnore = Pattern.compile(deployIgnore); } support.firePropertyChange("deployIgnore", oldDeployIgnore, deployIgnore); } // --------------------------------------------------------- Public Methods /** * Add an alias name that should be mapped to this same Host. * * @param alias The alias to be added */ public void addAlias(String alias) { alias = alias.toLowerCase(); synchronized (aliasesLock) { // Skip duplicate aliases for (int i = 0; i < aliases.length; i++) { if (aliases[i].equals(alias)) return; } // Add this alias to the list String newAliases[] = new String[aliases.length + 1]; for (int i = 0; i < aliases.length; i++) newAliases[i] = aliases[i]; newAliases[aliases.length] = alias; aliases = newAliases; } // Inform interested listeners fireContainerEvent(ADD_ALIAS_EVENT, alias); } /** * Add a child Container, only if the proposed child is an implementation * of Context. * * @param child Child container to be added */ public void addChild(Container child) { if (child instanceof Lifecycle) { ((Lifecycle) child).addLifecycleListener( new MemoryLeakTrackingListener()); } if (!(child instanceof Context)) throw new IllegalArgumentException (sm.getString("standardHost.notContext")); super.addChild(child); } /** * Used to ensure the regardless of {@link Context} implementation, a record * is kept of the class loader used every time a context starts. */ private class MemoryLeakTrackingListener implements LifecycleListener { public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { if (event.getSource() instanceof Context) { Context context = ((Context) event.getSource()); childClassLoaders.put(context.getLoader().getClassLoader(), context.getServletContext().getContextPath()); } } } } /** * Attempt to identify the contexts that have a class loader memory leak. * This is usually triggered on context reload. Note: This method attempts * to force a full garbage collection. This should be used with extreme * caution on a production system. */ public String[] findReloadedContextMemoryLeaks() { System.gc(); List<String> result = new ArrayList<String>(); for (Map.Entry<ClassLoader, String> entry : childClassLoaders.entrySet()) { ClassLoader cl = entry.getKey(); if (cl instanceof WebappClassLoader) { if (!((WebappClassLoader) cl).isStarted()) { result.add(entry.getValue()); } } } return result.toArray(new String[result.size()]); } /** * Return the set of alias names for this Host. If none are defined, * a zero length array is returned. */ public String[] findAliases() { synchronized (aliasesLock) { return (this.aliases); } } /** * Return descriptive information about this Container implementation and * the corresponding version number, in the format * <code><description>/<version></code>. */ public String getInfo() { return (info); } /** * Return the Context that would be used to process the specified * host-relative request URI, if any; otherwise return <code>null</code>. * * @param uri Request URI to be mapped */ public Context map(String uri) { if (log.isDebugEnabled()) log.debug("Mapping request URI '" + uri + "'"); if (uri == null) return (null); // Match on the longest possible context path prefix if (log.isTraceEnabled()) log.trace(" Trying the longest context path prefix"); Context context = null; String mapuri = uri; while (true) { context = (Context) findChild(mapuri); if (context != null) break; int slash = mapuri.lastIndexOf('/'); if (slash < 0) break; mapuri = mapuri.substring(0, slash); } // If no Context matches, select the default Context if (context == null) { if (log.isTraceEnabled()) log.trace(" Trying the default context"); context = (Context) findChild(""); } // Complain if no Context has been selected if (context == null) { log.error(sm.getString("standardHost.mappingError", uri)); return (null); } // Return the mapped Context (if any) if (log.isDebugEnabled()) log.debug(" Mapped to context '" + context.getPath() + "'"); return (context); } /** * Remove the specified alias name from the aliases for this Host. * * @param alias Alias name to be removed */ public void removeAlias(String alias) { alias = alias.toLowerCase(); synchronized (aliasesLock) { // Make sure this alias is currently present int n = -1; for (int i = 0; i < aliases.length; i++) { if (aliases[i].equals(alias)) { n = i; break; } } if (n < 0) return; // Remove the specified alias int j = 0; String results[] = new String[aliases.length - 1]; for (int i = 0; i < aliases.length; i++) { if (i != n) results[j++] = aliases[i]; } aliases = results; } // Inform interested listeners fireContainerEvent(REMOVE_ALIAS_EVENT, alias); } /** * Return a String representation of this component. */ public String toString() { StringBuffer sb = new StringBuffer(); if (getParent() != null) { sb.append(getParent().toString()); sb.append("."); } sb.append("StandardHost["); sb.append(getName()); sb.append("]"); return (sb.toString()); } /** * Start this host. * * @exception LifecycleException if this component detects a fatal error * that prevents it from being started */ public synchronized void start() throws LifecycleException { if( started ) { return; } if( ! initialized ) init(); // Look for a realm - that may have been configured earlier. // If the realm is added after context - it'll set itself. if( realm == null ) { ObjectName realmName=null; try { realmName=new ObjectName( domain + ":type=Realm,host=" + getName()); if( mserver.isRegistered(realmName ) ) { mserver.invoke(realmName, "init", new Object[] {}, new String[] {} ); } } catch( Throwable t ) { log.debug("No realm for this host " + realmName); } } // Set error report valve if ((errorReportValveClass != null) && (!errorReportValveClass.equals(""))) { try { boolean found = false; if(errorReportValveObjectName != null) { ObjectName[] names = ((StandardPipeline)pipeline).getValveObjectNames(); for (int i=0; !found && i<names.length; i++) if(errorReportValveObjectName.equals(names[i])) found = true ; } if(!found) { Valve valve = (Valve) Class.forName(errorReportValveClass) .newInstance(); addValve(valve); errorReportValveObjectName = ((ValveBase)valve).getObjectName() ; } } catch (Throwable t) { log.error(sm.getString ("standardHost.invalidErrorReportValveClass", errorReportValveClass), t); } } if(log.isDebugEnabled()) { if (xmlValidation) log.debug(sm.getString("standardHost.validationEnabled")); else log.debug(sm.getString("standardHost.validationDisabled")); } super.start(); } // -------------------- JMX -------------------- /** * Return the MBean Names of the Valves assoicated with this Host * * @exception Exception if an MBean cannot be created or registered */ public String [] getValveNames() throws Exception { Valve [] valves = this.getValves(); String [] mbeanNames = new String[valves.length]; for (int i = 0; i < valves.length; i++) { if( valves[i] == null ) continue; if( ((ValveBase)valves[i]).getObjectName() == null ) continue; mbeanNames[i] = ((ValveBase)valves[i]).getObjectName().toString(); } return mbeanNames; } public String[] getAliases() { synchronized (aliasesLock) { return aliases; } } private boolean initialized=false; public void init() { if( initialized ) return; initialized=true; // already registered. if( getParent() == null ) { try { // Register with the Engine ObjectName serviceName=new ObjectName(domain + ":type=Engine"); HostConfig deployer = new HostConfig(); addLifecycleListener(deployer); if( mserver.isRegistered( serviceName )) { if(log.isDebugEnabled()) log.debug("Registering "+ serviceName +" with the Engine"); mserver.invoke( serviceName, "addChild", new Object[] { this }, new String[] { "org.apache.catalina.Container" } ); } } catch( Exception ex ) { log.error("Host registering failed!",ex); } } if( oname==null ) { // not registered in JMX yet - standalone mode try { StandardEngine engine=(StandardEngine)parent; domain=engine.getName(); if(log.isDebugEnabled()) log.debug( "Register host " + getName() + " with domain "+ domain ); oname=new ObjectName(domain + ":type=Host,host=" + this.getName()); controller = oname; Registry.getRegistry(null, null) .registerComponent(this, oname, null); } catch( Throwable t ) { log.error("Host registering failed!", t ); } } } public void destroy() throws Exception { // destroy our child containers, if any Container children[] = findChildren(); super.destroy(); for (int i = 0; i < children.length; i++) { if(children[i] instanceof StandardContext) ((StandardContext)children[i]).destroy(); } } public ObjectName preRegister(MBeanServer server, ObjectName oname ) throws Exception { ObjectName res=super.preRegister(server, oname); String name=oname.getKeyProperty("host"); if( name != null ) setName( name ); return res; } public ObjectName createObjectName(String domain, ObjectName parent) throws Exception { if( log.isDebugEnabled()) log.debug("Create ObjectName " + domain + " " + parent ); return new ObjectName( domain + ":type=Host,host=" + getName()); } }