/* * 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.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.naming.Binding; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.annotation.HandlesTypes; import org.apache.catalina.Authenticator; 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.LifecycleListener; import org.apache.catalina.Pipeline; import org.apache.catalina.Server; import org.apache.catalina.Service; import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardHost; import org.apache.catalina.deploy.ErrorPage; import org.apache.catalina.deploy.FilterDef; import org.apache.catalina.deploy.FilterMap; import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.deploy.SecurityConstraint; import org.apache.catalina.deploy.ServletDef; import org.apache.catalina.deploy.WebXml; import org.apache.catalina.util.ContextName; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.naming.resources.DirContextURLConnection; import org.apache.naming.resources.FileDirContext; import org.apache.naming.resources.ResourceAttributes; import org.apache.tomcat.JarScanner; import org.apache.tomcat.JarScannerCallback; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.bcel.classfile.AnnotationElementValue; import org.apache.tomcat.util.bcel.classfile.AnnotationEntry; import org.apache.tomcat.util.bcel.classfile.ArrayElementValue; import org.apache.tomcat.util.bcel.classfile.ClassFormatException; import org.apache.tomcat.util.bcel.classfile.ClassParser; import org.apache.tomcat.util.bcel.classfile.ElementValue; import org.apache.tomcat.util.bcel.classfile.ElementValuePair; import org.apache.tomcat.util.bcel.classfile.JavaClass; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.digester.RuleSet; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.scan.Jar; import org.apache.tomcat.util.scan.JarFactory; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; /** * Startup event listener for a Context that configures the properties * of that Context, and the associated defined servlets. * * @author Craig R. McClanahan * @author Jean-Francois Arcand * @version $Id$ */ public class ContextConfig implements LifecycleListener { private static final Log log = LogFactory.getLog( ContextConfig.class ); private static final String SCI_LOCATION = "META-INF/services/javax.servlet.ServletContainerInitializer"; /** * The string resources for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); protected static final LoginConfig DUMMY_LOGIN_CONFIG = new LoginConfig("NONE", null, null, null); /** * The set of Authenticators that we know how to configure. The key is * the name of the implemented authentication method, and the value is * the fully qualified Java class name of the corresponding Valve. */ protected static final Properties authenticators; static { // Load our mapping properties for the standard authenticators InputStream is = ContextConfig.class.getClassLoader().getResourceAsStream( "org/apache/catalina/startup/Authenticators.properties"); Properties props = null; props = new Properties(); if (is != null) { try { props.load(is); } catch (IOException e) { props = null; } } authenticators = props; } /** * Deployment count. */ protected static long deploymentCount = 0L; /** * Cache of default web.xml fragments per Host */ protected static final Map hostWebXmlCache = new ConcurrentHashMap(); // ----------------------------------------------------- Instance Variables /** * Custom mappings of login methods to authenticators */ protected Map customAuthenticators; /** * The Context we are associated with. */ protected Context context = null; /** * The default web application's deployment descriptor location. */ protected String defaultWebXml = null; /** * Track any fatal errors during startup configuration processing. */ protected boolean ok = false; /** * Original docBase. */ protected String originalDocBase = null; /** * Map of ServletContainerInitializer to classes they expressed interest in. */ protected final Map>> initializerClassMap = new LinkedHashMap>>(); /** * Map of Types to ServletContainerInitializer that are interested in those * types. */ protected final Map, Set> typeInitializerMap = new HashMap, Set>(); /** * Cache of JavaClass objects (byte code) by fully qualified class name. * Only populated if it is necessary to scan the super types and interfaces * as part of the processing for {@link HandlesTypes}. */ protected final Map javaClassCache = new HashMap(); /** * Flag that indicates if at least one {@link HandlesTypes} entry is present * that represents an annotation. */ protected boolean handlesTypesAnnotations = false; /** * Flag that indicates if at least one {@link HandlesTypes} entry is present * that represents a non-annotation. */ protected boolean handlesTypesNonAnnotations = false; /** * The Digester we will use to process web application * deployment descriptor files. */ protected Digester webDigester = null; protected WebRuleSet webRuleSet = null; /** * The Digester we will use to process web fragment * deployment descriptor files. */ protected Digester webFragmentDigester = null; protected WebRuleSet webFragmentRuleSet = null; // ------------------------------------------------------------- Properties /** * Return the location of the default deployment descriptor */ public String getDefaultWebXml() { if( defaultWebXml == null ) { defaultWebXml=Constants.DefaultWebXml; } return (this.defaultWebXml); } /** * Set the location of the default deployment descriptor * * @param path Absolute/relative path to the default web.xml */ public void setDefaultWebXml(String path) { this.defaultWebXml = path; } /** * Sets custom mappings of login methods to authenticators. * * @param customAuthenticators Custom mappings of login methods to * authenticators */ public void setCustomAuthenticators( Map customAuthenticators) { this.customAuthenticators = customAuthenticators; } // --------------------------------------------------------- Public Methods /** * Process events for an associated Context. * * @param event The lifecycle event that has occurred */ @Override public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with try { context = (Context) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase != null) { String docBase = context.getDocBase(); context.setDocBase(originalDocBase); originalDocBase = docBase; } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { if (originalDocBase != null) { String docBase = context.getDocBase(); context.setDocBase(originalDocBase); originalDocBase = docBase; } configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } } // -------------------------------------------------------- protected Methods /** * Process the application classes annotations, if it exists. */ protected void applicationAnnotationsConfig() { long t1=System.currentTimeMillis(); WebAnnotationSet.loadApplicationAnnotations(context); long t2=System.currentTimeMillis(); if (context instanceof StandardContext) { ((StandardContext) context).setStartupTime(t2-t1+ ((StandardContext) context).getStartupTime()); } } /** * Set up an Authenticator automatically if required, and one has not * already been configured. */ protected void authenticatorConfig() { LoginConfig loginConfig = context.getLoginConfig(); SecurityConstraint constraints[] = context.findConstraints(); if (context.getIgnoreAnnotations() && (constraints == null || constraints.length ==0) && !context.getPreemptiveAuthentication()) { return; } else { if (loginConfig == null) { // Not metadata-complete or security constraints present, need // an authenticator to support @ServletSecurity annotations // and/or constraints loginConfig = DUMMY_LOGIN_CONFIG; context.setLoginConfig(loginConfig); } } // Has an authenticator been configured already? if (context.getAuthenticator() != null) { return; } // Has a Realm been configured for us to authenticate against? if (context.getRealm() == null) { log.error(sm.getString("contextConfig.missingRealm")); ok = false; return; } /* * First check to see if there is a custom mapping for the login * method. If so, use it. Otherwise, check if there is a mapping in * org/apache/catalina/startup/Authenticators.properties. */ Valve authenticator = null; if (customAuthenticators != null) { authenticator = (Valve) customAuthenticators.get(loginConfig.getAuthMethod()); } if (authenticator == null) { if (authenticators == null) { log.error(sm.getString("contextConfig.authenticatorResources")); ok = false; return; } // Identify the class name of the Valve we should configure String authenticatorName = null; authenticatorName = authenticators.getProperty(loginConfig.getAuthMethod()); if (authenticatorName == null) { log.error(sm.getString("contextConfig.authenticatorMissing", loginConfig.getAuthMethod())); ok = false; return; } // Instantiate and install an Authenticator of the requested class try { Class authenticatorClass = Class.forName(authenticatorName); authenticator = (Valve) authenticatorClass.newInstance(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "contextConfig.authenticatorInstantiate", authenticatorName), t); ok = false; } } if (authenticator != null) { Pipeline pipeline = context.getPipeline(); if (pipeline != null) { pipeline.addValve(authenticator); if (log.isDebugEnabled()) { log.debug(sm.getString( "contextConfig.authenticatorConfigured", loginConfig.getAuthMethod())); } } } } /** * Create and return a Digester configured to process the * web application deployment descriptor (web.xml). */ public void createWebXmlDigester(boolean namespaceAware, boolean validation) { webRuleSet = new WebRuleSet(false); webDigester = DigesterFactory.newDigester(validation, namespaceAware, webRuleSet); webDigester.getParser(); webFragmentRuleSet = new WebRuleSet(true); webFragmentDigester = DigesterFactory.newDigester(validation, namespaceAware, webFragmentRuleSet); webFragmentDigester.getParser(); } /** * Create (if necessary) and return a Digester configured to process the * context configuration descriptor for an application. */ protected Digester createContextDigester() { Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap, List> fakeAttributes = new HashMap, List>(); ArrayList attrs = new ArrayList(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); RuleSet contextRuleSet = new ContextRuleSet("", false); digester.addRuleSet(contextRuleSet); RuleSet namingRuleSet = new NamingRuleSet("Context/"); digester.addRuleSet(namingRuleSet); return digester; } /** * Process the default configuration file, if it exists. */ protected void contextConfig(Digester digester) { String defaultContextXml = null; // Open the default context.xml file, if it exists if (context instanceof StandardContext) { defaultContextXml = ((StandardContext)context).getDefaultContextXml(); } // set the default if we don't have any overrides if (defaultContextXml == null) { defaultContextXml = Constants.DefaultContextXml; } if (!context.getOverride()) { File defaultContextFile = new File(defaultContextXml); if (!defaultContextFile.isAbsolute()) { defaultContextFile = new File(context.getCatalinaBase(), defaultContextXml); } if (defaultContextFile.exists()) { try { URL defaultContextUrl = defaultContextFile.toURI().toURL(); processContextConfig(digester, defaultContextUrl); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.badUrl", defaultContextFile), e); } } File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml); if (hostContextFile.exists()) { try { URL hostContextUrl = hostContextFile.toURI().toURL(); processContextConfig(digester, hostContextUrl); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.badUrl", hostContextFile), e); } } } if (context.getConfigFile() != null) { processContextConfig(digester, context.getConfigFile()); } } /** * Process a context.xml. */ protected void processContextConfig(Digester digester, URL contextXml) { if (log.isDebugEnabled()) { log.debug("Processing context [" + context.getName() + "] configuration file [" + contextXml + "]"); } InputSource source = null; InputStream stream = null; try { source = new InputSource(contextXml.toString()); stream = contextXml.openStream(); } catch (Exception e) { log.error(sm.getString("contextConfig.contextMissing", contextXml) , e); } if (source == null) { return; } try { source.setByteStream(stream); digester.setClassLoader(this.getClass().getClassLoader()); digester.setUseContextClassLoader(false); digester.push(context.getParent()); digester.push(context); XmlErrorHandler errorHandler = new XmlErrorHandler(); digester.setErrorHandler(errorHandler); digester.parse(source); if (errorHandler.getWarnings().size() > 0 || errorHandler.getErrors().size() > 0) { errorHandler.logFindings(log, contextXml.toString()); ok = false; } if (log.isDebugEnabled()) { log.debug("Successfully processed context [" + context.getName() + "] configuration file [" + contextXml + "]"); } } catch (SAXParseException e) { log.error(sm.getString("contextConfig.contextParse", context.getName()), e); log.error(sm.getString("contextConfig.defaultPosition", "" + e.getLineNumber(), "" + e.getColumnNumber())); ok = false; } catch (Exception e) { log.error(sm.getString("contextConfig.contextParse", context.getName()), e); ok = false; } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) { log.error(sm.getString("contextConfig.contextClose"), e); } } } /** * Adjust docBase. */ protected void fixDocBase() throws IOException { Host host = (Host) context.getParent(); File appBase = host.getAppBaseFile(); String docBase = context.getDocBase(); if (docBase == null) { // Trying to guess the docBase according to the path String path = context.getPath(); if (path == null) { return; } ContextName cn = new ContextName(path, context.getWebappVersion()); docBase = cn.getBaseName(); } File file = new File(docBase); if (!file.isAbsolute()) { docBase = (new File(appBase, docBase)).getPath(); } else { docBase = file.getCanonicalPath(); } file = new File(docBase); String origDocBase = docBase; ContextName cn = new ContextName(context.getPath(), context.getWebappVersion()); String pathName = cn.getBaseName(); boolean unpackWARs = true; if (host instanceof StandardHost && context instanceof StandardContext) { unpackWARs = ((StandardHost) host).isUnpackWARs() && ((StandardContext) context).getUnpackWAR() && (docBase.startsWith(host.getAppBaseFile().getPath())); } if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory() && unpackWARs) { URL war = new URL("jar:" + (new File(docBase)).toURI().toURL() + "!/"); docBase = ExpandWar.expand(host, war, pathName); file = new File(docBase); docBase = file.getCanonicalPath(); if (context instanceof StandardContext) { ((StandardContext) context).setOriginalDocBase(origDocBase); } } else if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory() && !unpackWARs) { URL war = new URL("jar:" + (new File (docBase)).toURI().toURL() + "!/"); ExpandWar.validate(host, war, pathName); } else { File docDir = new File(docBase); if (!docDir.exists()) { File warFile = new File(docBase + ".war"); if (warFile.exists()) { URL war = new URL("jar:" + warFile.toURI().toURL() + "!/"); if (unpackWARs) { docBase = ExpandWar.expand(host, war, pathName); file = new File(docBase); docBase = file.getCanonicalPath(); } else { docBase = warFile.getCanonicalPath(); ExpandWar.validate(host, war, pathName); } } if (context instanceof StandardContext) { ((StandardContext) context).setOriginalDocBase(origDocBase); } } } if (docBase.startsWith(appBase.getPath() + File.separatorChar)) { docBase = docBase.substring(appBase.getPath().length()); docBase = docBase.replace(File.separatorChar, '/'); if (docBase.startsWith("/")) { docBase = docBase.substring(1); } } else { docBase = docBase.replace(File.separatorChar, '/'); } context.setDocBase(docBase); } protected void antiLocking() { if ((context instanceof StandardContext) && ((StandardContext) context).getAntiResourceLocking()) { Host host = (Host) context.getParent(); String docBase = context.getDocBase(); if (docBase == null) { return; } if (originalDocBase == null) { originalDocBase = docBase; } else { docBase = originalDocBase; } File docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) { docBaseFile = new File(host.getAppBaseFile(), docBase); } String path = context.getPath(); if (path == null) { return; } ContextName cn = new ContextName(path, context.getWebappVersion()); docBase = cn.getBaseName(); File file = null; if (originalDocBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) { file = new File(System.getProperty("java.io.tmpdir"), deploymentCount++ + "-" + docBase + ".war"); } else { file = new File(System.getProperty("java.io.tmpdir"), deploymentCount++ + "-" + docBase); } if (log.isDebugEnabled()) { log.debug("Anti locking context[" + context.getName() + "] setting docBase to " + file); } // Cleanup just in case an old deployment is lying around ExpandWar.delete(file); if (ExpandWar.copy(docBaseFile, file)) { context.setDocBase(file.getAbsolutePath()); } } } /** * Process a "init" event for this Context. */ protected void init() { // Called from StandardContext.init() Digester contextDigester = createContextDigester(); contextDigester.getParser(); if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.init")); } context.setConfigured(false); ok = true; contextConfig(contextDigester); createWebXmlDigester(context.getXmlNamespaceAware(), context.getXmlValidation()); try { fixDocBase(); } catch (IOException e) { log.error(sm.getString( "contextConfig.fixDocBase", context.getName()), e); } } /** * Process a "before start" event for this Context. */ protected synchronized void beforeStart() { antiLocking(); } /** * Process a "contextConfig" event for this Context. */ protected synchronized void configureStart() { // Called from StandardContext.start() if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.start")); } if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.xmlSettings", context.getName(), Boolean.valueOf(context.getXmlValidation()), Boolean.valueOf(context.getXmlNamespaceAware()))); } webConfig(); if (!context.getIgnoreAnnotations()) { applicationAnnotationsConfig(); } if (ok) { validateSecurityRoles(); } // Configure an authenticator if we need one if (ok) { authenticatorConfig(); } // Dump the contents of this pipeline if requested if (log.isDebugEnabled()) { log.debug("Pipeline Configuration:"); Pipeline pipeline = context.getPipeline(); Valve valves[] = null; if (pipeline != null) { valves = pipeline.getValves(); } if (valves != null) { for (int i = 0; i < valves.length; i++) { log.debug(" " + valves[i].getClass().getName()); } } log.debug("======================"); } // Make our application available if no problems were encountered if (ok) { context.setConfigured(true); } else { log.error(sm.getString("contextConfig.unavailable")); context.setConfigured(false); } } /** * Process a "stop" event for this Context. */ protected synchronized void configureStop() { if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.stop")); } int i; // Removing children Container[] children = context.findChildren(); for (i = 0; i < children.length; i++) { context.removeChild(children[i]); } // Removing application parameters /* ApplicationParameter[] applicationParameters = context.findApplicationParameters(); for (i = 0; i < applicationParameters.length; i++) { context.removeApplicationParameter (applicationParameters[i].getName()); } */ // Removing security constraints SecurityConstraint[] securityConstraints = context.findConstraints(); for (i = 0; i < securityConstraints.length; i++) { context.removeConstraint(securityConstraints[i]); } // Removing Ejbs /* ContextEjb[] contextEjbs = context.findEjbs(); for (i = 0; i < contextEjbs.length; i++) { context.removeEjb(contextEjbs[i].getName()); } */ // Removing environments /* ContextEnvironment[] contextEnvironments = context.findEnvironments(); for (i = 0; i < contextEnvironments.length; i++) { context.removeEnvironment(contextEnvironments[i].getName()); } */ // Removing errors pages ErrorPage[] errorPages = context.findErrorPages(); for (i = 0; i < errorPages.length; i++) { context.removeErrorPage(errorPages[i]); } // Removing filter defs FilterDef[] filterDefs = context.findFilterDefs(); for (i = 0; i < filterDefs.length; i++) { context.removeFilterDef(filterDefs[i]); } // Removing filter maps FilterMap[] filterMaps = context.findFilterMaps(); for (i = 0; i < filterMaps.length; i++) { context.removeFilterMap(filterMaps[i]); } // Removing local ejbs /* ContextLocalEjb[] contextLocalEjbs = context.findLocalEjbs(); for (i = 0; i < contextLocalEjbs.length; i++) { context.removeLocalEjb(contextLocalEjbs[i].getName()); } */ // Removing Mime mappings String[] mimeMappings = context.findMimeMappings(); for (i = 0; i < mimeMappings.length; i++) { context.removeMimeMapping(mimeMappings[i]); } // Removing parameters String[] parameters = context.findParameters(); for (i = 0; i < parameters.length; i++) { context.removeParameter(parameters[i]); } // Removing resource env refs /* String[] resourceEnvRefs = context.findResourceEnvRefs(); for (i = 0; i < resourceEnvRefs.length; i++) { context.removeResourceEnvRef(resourceEnvRefs[i]); } */ // Removing resource links /* ContextResourceLink[] contextResourceLinks = context.findResourceLinks(); for (i = 0; i < contextResourceLinks.length; i++) { context.removeResourceLink(contextResourceLinks[i].getName()); } */ // Removing resources /* ContextResource[] contextResources = context.findResources(); for (i = 0; i < contextResources.length; i++) { context.removeResource(contextResources[i].getName()); } */ // Removing security role String[] securityRoles = context.findSecurityRoles(); for (i = 0; i < securityRoles.length; i++) { context.removeSecurityRole(securityRoles[i]); } // Removing servlet mappings String[] servletMappings = context.findServletMappings(); for (i = 0; i < servletMappings.length; i++) { context.removeServletMapping(servletMappings[i]); } // FIXME : Removing status pages // Removing welcome files String[] welcomeFiles = context.findWelcomeFiles(); for (i = 0; i < welcomeFiles.length; i++) { context.removeWelcomeFile(welcomeFiles[i]); } // Removing wrapper lifecycles String[] wrapperLifecycles = context.findWrapperLifecycles(); for (i = 0; i < wrapperLifecycles.length; i++) { context.removeWrapperLifecycle(wrapperLifecycles[i]); } // Removing wrapper listeners String[] wrapperListeners = context.findWrapperListeners(); for (i = 0; i < wrapperListeners.length; i++) { context.removeWrapperListener(wrapperListeners[i]); } // Remove (partially) folders and files created by antiLocking Host host = (Host) context.getParent(); String docBase = context.getDocBase(); if ((docBase != null) && (originalDocBase != null)) { File docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) { docBaseFile = new File(host.getAppBaseFile(), docBase); } // No need to log failure - it is expected in this case ExpandWar.delete(docBaseFile, false); } // Reset ServletContextInitializer scanning initializerClassMap.clear(); typeInitializerMap.clear(); ok = true; } /** * Process a "destroy" event for this Context. */ protected synchronized void destroy() { // Called from StandardContext.destroy() if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.destroy")); } // Skip clearing the work directory if Tomcat is being shutdown Server s = getServer(); if (s != null && !s.getState().isAvailable()) { return; } // Changed to getWorkPath per Bugzilla 35819. String workDir = ((StandardContext) context).getWorkPath(); if (workDir != null) { ExpandWar.delete(new File(workDir)); } } private Server getServer() { Container c = context; while (c != null && !(c instanceof Engine)) { c = c.getParent(); } if (c == null) { return null; } Service s = ((Engine)c).getService(); if (s == null) { return null; } return s.getServer(); } /** * Validate the usage of security role names in the web application * deployment descriptor. If any problems are found, issue warning * messages (for backwards compatibility) and add the missing roles. * (To make these problems fatal instead, simply set the ok * instance variable to false as well). */ protected void validateSecurityRoles() { // Check role names used in elements SecurityConstraint constraints[] = context.findConstraints(); for (int i = 0; i < constraints.length; i++) { String roles[] = constraints[i].findAuthRoles(); for (int j = 0; j < roles.length; j++) { if (!"*".equals(roles[j]) && !context.findSecurityRole(roles[j])) { log.info(sm.getString("contextConfig.role.auth", roles[j])); context.addSecurityRole(roles[j]); } } } // Check role names used in elements Container wrappers[] = context.findChildren(); for (int i = 0; i < wrappers.length; i++) { Wrapper wrapper = (Wrapper) wrappers[i]; String runAs = wrapper.getRunAs(); if ((runAs != null) && !context.findSecurityRole(runAs)) { log.info(sm.getString("contextConfig.role.runas", runAs)); context.addSecurityRole(runAs); } String names[] = wrapper.findSecurityReferences(); for (int j = 0; j < names.length; j++) { String link = wrapper.findSecurityReference(names[j]); if ((link != null) && !context.findSecurityRole(link)) { log.info(sm.getString("contextConfig.role.link", link)); context.addSecurityRole(link); } } } } /** * Get config base. */ protected File getConfigBase() { File catalinaBase = context.getCatalinaBase(); if (catalinaBase == null) { return null; } File configBase = new File(catalinaBase, "conf"); if (!configBase.exists()) { return null; } return configBase; } protected File getHostConfigBase() { File file = null; Container container = context; Host host = null; Engine engine = null; while (container != null) { if (container instanceof Host) { host = (Host)container; } if (container instanceof Engine) { engine = (Engine)container; } container = container.getParent(); } if (host != null && host.getXmlBase()!=null) { String xmlBase = host.getXmlBase(); file = new File(xmlBase); if (!file.isAbsolute()) file = new File(context.getCatalinaBase(), xmlBase); } else { StringBuilder result = new StringBuilder(); if (engine != null) { result.append(engine.getName()).append('/'); } if (host != null) { result.append(host.getName()).append('/'); } file = new File (getConfigBase(), result.toString()); } try { return file.getCanonicalFile(); } catch (IOException e) { return file; } } /** * Scan the web.xml files that apply to the web application and merge them * using the rules defined in the spec. For the global web.xml files, * where there is duplicate configuration, the most specific level wins. ie * an application's web.xml takes precedence over the host level or global * web.xml file. */ protected void webConfig() { /* Anything and everything can override the global and host defaults. * This is implemented in two parts * - Handle as a web fragment that gets added after everything else so * everything else takes priority * - Mark Servlets as overridable so SCI configuration can replace * configuration from the defaults */ Set defaults = new HashSet(); defaults.add(getDefaultWebXmlFragment()); WebXml webXml = createWebXml(); // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource(); parseWebXml(contextWebXml, webXml, false); if (webXml.getMajorVersion() >= 3) { ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application // If the JARs have a web-fragment.xml it will be parsed at this // point. Map fragments = processJarsForWebFragments(); // Only need to process fragments and annotations if metadata is // not complete Set orderedFragments = null; if (!webXml.isMetadataComplete()) { // Step 2. Order the fragments. orderedFragments = WebXml.orderWebFragments(webXml, fragments); // Step 3. Look for ServletContainerInitializer implementations if (ok) { processServletContainerInitializers(orderedFragments); } // Step 4. Process /WEB-INF/classes for annotations // This will add any matching classes to the typeInitializerMap if (ok) { // Hack required by Eclipse's "serve modules without // publishing" feature since this backs WEB-INF/classes by // multiple locations rather than one. NamingEnumeration listBindings = null; try { try { listBindings = context.getResources().listBindings( "/WEB-INF/classes"); } catch (NameNotFoundException ignore) { // Safe to ignore } while (listBindings != null && listBindings.hasMoreElements()) { Binding binding = listBindings.nextElement(); if (binding.getObject() instanceof FileDirContext) { File webInfClassDir = new File( ((FileDirContext) binding.getObject()).getDocBase()); processAnnotationsFile(webInfClassDir, webXml); } else { String resource = "/WEB-INF/classes/" + binding.getName(); try { URL url = sContext.getResource(resource); processAnnotationsUrl(url, webXml); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.webinfClassesUrl", resource), e); } } } } catch (NamingException e) { log.error(sm.getString( "contextConfig.webinfClassesUrl", "/WEB-INF/classes"), e); } } // Step 5. Process JARs for annotations - only need to process // those fragments we are going to use // This will add any matching classes to the typeInitializerMap if (ok) { processAnnotations(orderedFragments); } // Cache, if used, is no longer required so clear it javaClassCache.clear(); // Step 6. Merge web-fragment.xml files into the main web.xml // file. if (ok) { ok = webXml.merge(orderedFragments); } // Step 7. Apply global defaults // Have to merge defaults before JSP conversion since defaults // provide JSP servlet definition. webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { webXml.configureContext(context); // Step 9a. Make the merged web.xml available to other // components, specifically Jasper, to save those components // from having to re-generate it. // TODO Use a ServletContainerInitializer for Jasper String mergedWebXml = webXml.toXml(); sContext.setAttribute( org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, mergedWebXml); if (context.getLogEffectiveWebXml()) { log.info("web.xml:\n" + mergedWebXml); } } } else { webXml.merge(defaults); webXml.configureContext(context); } // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set resourceJars = new LinkedHashSet(); if (orderedFragments != null) { for (WebXml fragment : orderedFragments) { resourceJars.add(fragment); } } for (WebXml fragment : fragments.values()) { if (!resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration } // Only look for ServletContainerInitializer if metadata is not // complete if (!webXml.isMetadataComplete()) { // Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } } } else { // Apply unmerged web.xml to Context webXml.merge(defaults); convertJsps(webXml); webXml.configureContext(context); } } private WebXml getDefaultWebXmlFragment() { // Host should never be null Host host = (Host) context.getParent(); DefaultWebXmlCacheEntry entry = hostWebXmlCache.get(host); InputSource globalWebXml = getGlobalWebXmlSource(); InputSource hostWebXml = getHostWebXmlSource(); long globalTimeStamp = 0; long hostTimeStamp = 0; if (globalWebXml != null) { try { URL url = new URL(globalWebXml.getSystemId()); globalTimeStamp = url.openConnection().getLastModified(); } catch (MalformedURLException e) { globalTimeStamp = -1; } catch (IOException e) { globalTimeStamp = -1; } } if (hostWebXml != null) { try { URL url = new URL(hostWebXml.getSystemId()); hostTimeStamp = url.openConnection().getLastModified(); } catch (MalformedURLException e) { hostTimeStamp = -1; } catch (IOException e) { hostTimeStamp = -1; } } if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp && entry.getHostTimeStamp() == hostTimeStamp) { return entry.getWebXml(); } // Parsing global web.xml is relatively expensive. Use a sync block to // make sure it only happens once. Use the pipeline since a lock will // already be held on the host by another thread synchronized (host.getPipeline()) { entry = hostWebXmlCache.get(host); if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp && entry.getHostTimeStamp() == hostTimeStamp) { return entry.getWebXml(); } WebXml webXmlDefaultFragment = createWebXml(); webXmlDefaultFragment.setOverridable(true); // Set to distributable else every app will be prevented from being // distributable when the default fragment is merged with the main // web.xml webXmlDefaultFragment.setDistributable(true); // When merging, the default welcome files are only used if the app has // not defined any welcomes files. webXmlDefaultFragment.setAlwaysAddWelcomeFiles(false); // Parse global web.xml if present if (globalWebXml == null) { // This is unusual enough to log log.info(sm.getString("contextConfig.defaultMissing")); } else { parseWebXml(globalWebXml, webXmlDefaultFragment, false); } // Parse host level web.xml if present // Additive apart from welcome pages webXmlDefaultFragment.setReplaceWelcomeFiles(true); parseWebXml(hostWebXml, webXmlDefaultFragment, false); // Don't update the cache if an error occurs if (globalTimeStamp != -1 && hostTimeStamp != -1) { entry = new DefaultWebXmlCacheEntry(webXmlDefaultFragment, globalTimeStamp, hostTimeStamp); hostWebXmlCache.put(host, entry); } return webXmlDefaultFragment; } } private void convertJsps(WebXml webXml) { Map jspInitParams; ServletDef jspServlet = webXml.getServlets().get("jsp"); if (jspServlet == null) { jspInitParams = new HashMap(); Wrapper w = (Wrapper) context.findChild("jsp"); if (w != null) { String[] params = w.findInitParameters(); for (String param : params) { jspInitParams.put(param, w.findInitParameter(param)); } } } else { jspInitParams = jspServlet.getParameterMap(); } for (ServletDef servletDef: webXml.getServlets().values()) { if (servletDef.getJspFile() != null) { convertJsp(servletDef, jspInitParams); } } } private void convertJsp(ServletDef servletDef, Map jspInitParams) { servletDef.setServletClass(org.apache.catalina.core.Constants.JSP_SERVLET_CLASS); String jspFile = servletDef.getJspFile(); if ((jspFile != null) && !jspFile.startsWith("/")) { if (context.isServlet22()) { if(log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.jspFile.warning", jspFile)); } jspFile = "/" + jspFile; } else { throw new IllegalArgumentException (sm.getString("contextConfig.jspFile.error", jspFile)); } } servletDef.getParameterMap().put("jspFile", jspFile); servletDef.setJspFile(null); for (Map.Entry initParam: jspInitParams.entrySet()) { servletDef.addInitParameter(initParam.getKey(), initParam.getValue()); } } protected WebXml createWebXml() { return new WebXml(); } /** * Scan JARs for ServletContainerInitializer implementations. * Implementations will be added in web-fragment.xml priority order. */ protected void processServletContainerInitializers( Set fragments) { for (WebXml fragment : fragments) { URL url = fragment.getURL(); Jar jar = null; InputStream is = null; ServletContainerInitializer sci = null; try { if ("jar".equals(url.getProtocol())) { jar = JarFactory.newInstance(url); is = jar.getInputStream(SCI_LOCATION); } else if ("file".equals(url.getProtocol())) { String path = url.getPath(); File file = new File(path, SCI_LOCATION); if (file.exists()) { is = new FileInputStream(file); } } if (is != null) { sci = getServletContainerInitializer(is); } } catch (IOException ioe) { log.error(sm.getString( "contextConfig.servletContainerInitializerFail", url, context.getName())); ok = false; return; } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Ignore } } if (jar != null) { jar.close(); } } if (sci == null) { continue; } initializerClassMap.put(sci, new HashSet>()); HandlesTypes ht = sci.getClass().getAnnotation(HandlesTypes.class); if (ht != null) { Class[] types = ht.value(); if (types != null) { for (Class type : types) { if (type.isAnnotation()) { handlesTypesAnnotations = true; } else { handlesTypesNonAnnotations = true; } Set scis = typeInitializerMap.get(type); if (scis == null) { scis = new HashSet(); typeInitializerMap.put(type, scis); } scis.add(sci); } } } } } /** * Extract the name of the ServletContainerInitializer. * * @param is The resource where the name is defined * @return The class name * @throws IOException */ protected ServletContainerInitializer getServletContainerInitializer( InputStream is) throws IOException { String className = null; if (is != null) { String line = null; try { BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); line = br.readLine(); if (line != null && line.trim().length() > 0) { className = line.trim(); } } catch (UnsupportedEncodingException e) { // Should never happen with UTF-8 // If it does - ignore & return null } } ServletContainerInitializer sci = null; try { Class clazz = Class.forName(className,true, context.getLoader().getClassLoader()); sci = (ServletContainerInitializer) clazz.newInstance(); } catch (ClassNotFoundException e) { log.error(sm.getString("contextConfig.invalidSci", className), e); throw new IOException(e); } catch (InstantiationException e) { log.error(sm.getString("contextConfig.invalidSci", className), e); throw new IOException(e); } catch (IllegalAccessException e) { log.error(sm.getString("contextConfig.invalidSci", className), e); throw new IOException(e); } return sci; } /** * Scan JARs that contain web-fragment.xml files that will be used to * configure this application to see if they also contain static resources. * If static resources are found, add them to the context. Resources are * added in web-fragment.xml priority order. */ protected void processResourceJARs(Set fragments) { for (WebXml fragment : fragments) { URL url = fragment.getURL(); Jar jar = null; try { // Note: Ignore file URLs for now since only jar URLs will be accepted if ("jar".equals(url.getProtocol())) { jar = JarFactory.newInstance(url); if (jar.entryExists("META-INF/resources/")) { context.addResourceJarUrl(url); } } else if ("file".equals(url.getProtocol())) { FileDirContext fileDirContext = new FileDirContext(); fileDirContext.setDocBase(new File(url.toURI()).getAbsolutePath()); try { fileDirContext.lookup("META-INF/resources/"); //lookup succeeded if(context instanceof StandardContext){ ((StandardContext)context).addResourcesDirContext(fileDirContext); } } catch (NamingException e) { //not found, ignore } } } catch (IOException ioe) { log.error(sm.getString("contextConfig.resourceJarFail", url, context.getName())); } catch (URISyntaxException e) { log.error(sm.getString("contextConfig.resourceJarFail", url, context.getName())); } finally { if (jar != null) { jar.close(); } } } } /** * Identify the default web.xml to be used and obtain an input source for * it. */ protected InputSource getGlobalWebXmlSource() { // Is a default web.xml specified for the Context? if (defaultWebXml == null && context instanceof StandardContext) { defaultWebXml = ((StandardContext) context).getDefaultWebXml(); } // Set the default if we don't have any overrides if (defaultWebXml == null) { getDefaultWebXml(); } // Is it explicitly suppressed, e.g. in embedded environment? if (Constants.NoDefaultWebXml.equals(defaultWebXml)) { return null; } return getWebXmlSource(defaultWebXml, context.getCatalinaBase().getPath()); } /** * Identify the host web.xml to be used and obtain an input source for * it. */ protected InputSource getHostWebXmlSource() { String basePath = null; try { basePath = getHostConfigBase().getCanonicalPath(); } catch (IOException e) { log.error(sm.getString("contextConfig.baseError"), e); return null; } return getWebXmlSource(Constants.HostWebXml, basePath); } /** * Identify the application web.xml to be used and obtain an input source * for it. */ protected InputSource getContextWebXmlSource() { InputStream stream = null; InputSource source = null; URL url = null; String altDDName = null; // Open the application web.xml file, if it exists ServletContext servletContext = context.getServletContext(); if (servletContext != null) { altDDName = (String)servletContext.getAttribute( Globals.ALT_DD_ATTR); if (altDDName != null) { try { stream = new FileInputStream(altDDName); url = new File(altDDName).toURI().toURL(); } catch (FileNotFoundException e) { log.error(sm.getString("contextConfig.altDDNotFound", altDDName)); } catch (MalformedURLException e) { log.error(sm.getString("contextConfig.applicationUrl")); } } else { stream = servletContext.getResourceAsStream (Constants.ApplicationWebXml); try { url = servletContext.getResource( Constants.ApplicationWebXml); } catch (MalformedURLException e) { log.error(sm.getString("contextConfig.applicationUrl")); } } } if (stream == null || url == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.applicationMissing") + " " + context); } } else { source = new InputSource(url.toExternalForm()); source.setByteStream(stream); } return source; } /** * * @param filename Name of the file (possibly with one or more leading path * segments) to read * @param path Location that filename is relative to */ protected InputSource getWebXmlSource(String filename, String path) { File file = new File(filename); if (!file.isAbsolute()) { file = new File(path, filename); } InputStream stream = null; InputSource source = null; try { if (!file.exists()) { // Use getResource and getResourceAsStream stream = getClass().getClassLoader().getResourceAsStream(filename); if(stream != null) { source = new InputSource(getClass().getClassLoader().getResource( filename).toURI().toString()); } } else { source = new InputSource(file.getAbsoluteFile().toURI().toString()); stream = new FileInputStream(file); } if (stream != null && source != null) { source.setByteStream(stream); } } catch (Exception e) { log.error(sm.getString( "contextConfig.defaultError", filename, file), e); } return source; } protected void parseWebXml(InputSource source, WebXml dest, boolean fragment) { if (source == null) { return; } XmlErrorHandler handler = new XmlErrorHandler(); Digester digester; WebRuleSet ruleSet; if (fragment) { digester = webFragmentDigester; ruleSet = webFragmentRuleSet; } else { digester = webDigester; ruleSet = webRuleSet; } digester.push(dest); digester.setErrorHandler(handler); if(log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.applicationStart", source.getSystemId())); } try { digester.parse(source); if (handler.getWarnings().size() > 0 || handler.getErrors().size() > 0) { ok = false; handler.logFindings(log, source.getSystemId()); } } catch (SAXParseException e) { log.error(sm.getString("contextConfig.applicationParse", source.getSystemId()), e); log.error(sm.getString("contextConfig.applicationPosition", "" + e.getLineNumber(), "" + e.getColumnNumber())); ok = false; } catch (Exception e) { log.error(sm.getString("contextConfig.applicationParse", source.getSystemId()), e); ok = false; } finally { digester.reset(); ruleSet.recycle(); } } /** * Scan /META-INF/lib for JARs and for each one found add it and any * /META-INF/web-fragment.xml to the resulting Map. web-fragment.xml files * will be parsed before being added to the map. Every JAR will be added and * null will be used if no web-fragment.xml was found. Any JARs * known not contain fragments will be skipped. * * @return A map of JAR name to processed web fragment (if any) */ protected Map processJarsForWebFragments() { JarScanner jarScanner = context.getJarScanner(); FragmentJarScannerCallback callback = new FragmentJarScannerCallback(); jarScanner.scan(context.getServletContext(), context.getLoader().getClassLoader(), callback, null); return callback.getFragments(); } protected void processAnnotations(Set fragments) { for(WebXml fragment : fragments) { if (!fragment.isMetadataComplete()) { WebXml annotations = new WebXml(); // no impact on distributable annotations.setDistributable(true); URL url = fragment.getURL(); processAnnotationsUrl(url, annotations); Set set = new HashSet(); set.add(annotations); // Merge annotations into fragment - fragment takes priority fragment.merge(set); } } } protected void processAnnotationsUrl(URL url, WebXml fragment) { if (url == null) { // Nothing to do. return; } else if ("jar".equals(url.getProtocol())) { processAnnotationsJar(url, fragment); } else if ("jndi".equals(url.getProtocol())) { processAnnotationsJndi(url, fragment); } else if ("file".equals(url.getProtocol())) { try { processAnnotationsFile(new File(url.toURI()), fragment); } catch (URISyntaxException e) { log.error(sm.getString("contextConfig.fileUrl", url), e); } } else { log.error(sm.getString("contextConfig.unknownUrlProtocol", url.getProtocol(), url)); } } protected void processAnnotationsJar(URL url, WebXml fragment) { Jar jar = null; InputStream is; try { jar = JarFactory.newInstance(url); jar.nextEntry(); String entryName = jar.getEntryName(); while (entryName != null) { if (entryName.endsWith(".class")) { is = null; try { is = jar.getEntryInputStream(); processAnnotationsStream(is, fragment); } catch (IOException e) { log.error(sm.getString("contextConfig.inputStreamJar", entryName, url),e); } finally { if (is != null) { try { is.close(); } catch (IOException ioe) { // Ignore } } } } jar.nextEntry(); entryName = jar.getEntryName(); } } catch (IOException e) { log.error(sm.getString("contextConfig.jarFile", url), e); } finally { if (jar != null) { jar.close(); } } } protected void processAnnotationsJndi(URL url, WebXml fragment) { try { URLConnection urlConn = url.openConnection(); DirContextURLConnection dcUrlConn; if (!(urlConn instanceof DirContextURLConnection)) { // This should never happen sm.getString("contextConfig.jndiUrlNotDirContextConn", url); return; } dcUrlConn = (DirContextURLConnection) urlConn; dcUrlConn.setUseCaches(false); String type = dcUrlConn.getHeaderField(ResourceAttributes.TYPE); if (ResourceAttributes.COLLECTION_TYPE.equals(type)) { // Collection Enumeration dirs = dcUrlConn.list(); while (dirs.hasMoreElements()) { String dir = dirs.nextElement(); URL dirUrl = new URL(url.toString() + '/' + dir); processAnnotationsJndi(dirUrl, fragment); } } else { // Single file if (url.getPath().endsWith(".class")) { InputStream is = null; try { is = dcUrlConn.getInputStream(); processAnnotationsStream(is, fragment); } catch (IOException e) { log.error(sm.getString("contextConfig.inputStreamJndi", url),e); } finally { if (is != null) { try { is.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } } } } } } catch (IOException e) { log.error(sm.getString("contextConfig.jndiUrl", url), e); } } protected void processAnnotationsFile(File file, WebXml fragment) { if (file.isDirectory()) { String[] dirs = file.list(); for (String dir : dirs) { processAnnotationsFile(new File(file,dir), fragment); } } else if (file.canRead() && file.getName().endsWith(".class")) { FileInputStream fis = null; try { fis = new FileInputStream(file); processAnnotationsStream(fis, fragment); } catch (IOException e) { log.error(sm.getString("contextConfig.inputStreamFile", file.getAbsolutePath()),e); } finally { if (fis != null) { try { fis.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } } } } } protected void processAnnotationsStream(InputStream is, WebXml fragment) throws ClassFormatException, IOException { ClassParser parser = new ClassParser(is, null); JavaClass clazz = parser.parse(); checkHandlesTypes(clazz); String className = clazz.getClassName(); AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries(); for (AnnotationEntry ae : annotationsEntries) { String type = ae.getAnnotationType(); if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) { processAnnotationWebServlet(className, ae, fragment); }else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) { processAnnotationWebFilter(className, ae, fragment); }else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) { fragment.addListener(className); } else { // Unknown annotation - ignore } } } /** * For classes packaged with the web application, the class and each * super class needs to be checked for a match with {@link HandlesTypes} or * for an annotation that matches {@link HandlesTypes}. * @param javaClass */ protected void checkHandlesTypes(JavaClass javaClass) { // Skip this if we can if (typeInitializerMap.size() == 0) { return; } if ((javaClass.getAccessFlags() & org.apache.tomcat.util.bcel.Constants.ACC_ANNOTATION) > 0) { // Skip annotations. return; } String className = javaClass.getClassName(); Class clazz = null; if (handlesTypesNonAnnotations) { // This *might* be match for a HandlesType. populateJavaClassCache(className, javaClass); JavaClassCacheEntry entry = javaClassCache.get(className); if (entry.getSciSet() == null) { populateSCIsForCacheEntry(entry); } if (entry.getSciSet().size() > 0) { // Need to try and load the class clazz = loadClass(className); if (clazz == null) { // Can't load the class so no point continuing return; } for (ServletContainerInitializer sci : entry.getSciSet()) { Set> classes = initializerClassMap.get(sci); if (classes == null) { classes = new HashSet>(); initializerClassMap.put(sci, classes); } classes.add(clazz); } } } if (handlesTypesAnnotations) { for (Map.Entry, Set> entry : typeInitializerMap.entrySet()) { if (entry.getKey().isAnnotation()) { AnnotationEntry[] annotationEntries = javaClass.getAnnotationEntries(); for (AnnotationEntry annotationEntry : annotationEntries) { if (entry.getKey().getName().equals( getClassName(annotationEntry.getAnnotationType()))) { if (clazz == null) { clazz = loadClass(className); if (clazz == null) { // Can't load the class so no point // continuing return; } } for (ServletContainerInitializer sci : entry.getValue()) { initializerClassMap.get(sci).add(clazz); } break; } } } } } } private void populateJavaClassCache(String className, JavaClass javaClass) { if (javaClassCache.containsKey(className)) { return; } // Add this class to the cache javaClassCache.put(className, new JavaClassCacheEntry(javaClass)); populateJavaClassCache(javaClass.getSuperclassName()); for (String iterface : javaClass.getInterfaceNames()) { populateJavaClassCache(iterface); } } private void populateJavaClassCache(String className) { if (!javaClassCache.containsKey(className)) { String name = className.replace('.', '/') + ".class"; InputStream is = context.getLoader().getClassLoader().getResourceAsStream(name); if (is == null) { return; } ClassParser parser = new ClassParser(is, null); try { JavaClass clazz = parser.parse(); populateJavaClassCache(clazz.getClassName(), clazz); } catch (ClassFormatException e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); } catch (IOException e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); } } } private void populateSCIsForCacheEntry(JavaClassCacheEntry cacheEntry) { Set result = new HashSet(); JavaClass javaClass = cacheEntry.getJavaClass(); // Super class String superClassName = javaClass.getSuperclassName(); JavaClassCacheEntry superClassCacheEntry = javaClassCache.get(superClassName); // Avoid an infinite loop with java.lang.Object if (cacheEntry.equals(superClassCacheEntry)) { cacheEntry.setSciSet(new HashSet()); return; } // May be null of the class is not present or could not be loaded. if (superClassCacheEntry != null) { if (superClassCacheEntry.getSciSet() == null) { populateSCIsForCacheEntry(superClassCacheEntry); } result.addAll(superClassCacheEntry.getSciSet()); } result.addAll(getSCIsForClass(superClassName)); // Interfaces for (String interfaceName : javaClass.getInterfaceNames()) { JavaClassCacheEntry interfaceEntry = javaClassCache.get(interfaceName); // A null could mean that the class not present in application or // that there is nothing of interest. Either way, nothing to do here // so move along if (interfaceEntry != null) { if (interfaceEntry.getSciSet() == null) { populateSCIsForCacheEntry(interfaceEntry); } result.addAll(interfaceEntry.getSciSet()); } result.addAll(getSCIsForClass(interfaceName)); } cacheEntry.setSciSet(result); } private Set getSCIsForClass(String className) { for (Map.Entry, Set> entry : typeInitializerMap.entrySet()) { Class clazz = entry.getKey(); if (!clazz.isAnnotation()) { if (clazz.getName().equals(className)) { return entry.getValue(); } } } return Collections.emptySet(); } private Class loadClass(String className) { Class clazz = null; try { clazz = context.getLoader().getClassLoader().loadClass(className); } catch (NoClassDefFoundError e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); return null; } catch (ClassNotFoundException e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); return null; } catch (ClassFormatError e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); return null; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), t); return null; } return clazz; } private static final String getClassName(String internalForm) { if (!internalForm.startsWith("L")) { return internalForm; } // Assume starts with L, ends with ; and uses / rather than . return internalForm.substring(1, internalForm.length() - 1).replace('/', '.'); } protected void processAnnotationWebServlet(String className, AnnotationEntry ae, WebXml fragment) { String servletName = null; // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81 ElementValuePair[] evps = ae.getElementValuePairs(); for (ElementValuePair evp : evps) { String name = evp.getNameString(); if ("name".equals(name)) { servletName = evp.getValue().stringifyValue(); break; } } if (servletName == null) { // classname is default servletName as annotation has no name! servletName = className; } ServletDef servletDef = fragment.getServlets().get(servletName); boolean isWebXMLservletDef; if (servletDef == null) { servletDef = new ServletDef(); servletDef.setServletName(servletName); servletDef.setServletClass(className); isWebXMLservletDef = false; } else { isWebXMLservletDef = true; } boolean urlPatternsSet = false; String[] urlPatterns = null; // ElementValuePair[] evps = ae.getElementValuePairs(); for (ElementValuePair evp : evps) { String name = evp.getNameString(); if ("value".equals(name) || "urlPatterns".equals(name)) { if (urlPatternsSet) { throw new IllegalArgumentException(sm.getString( "contextConfig.urlPatternValue", className)); } urlPatternsSet = true; urlPatterns = processAnnotationsStringArray(evp.getValue()); } else if ("description".equals(name)) { if (servletDef.getDescription() == null) { servletDef.setDescription(evp.getValue().stringifyValue()); } } else if ("displayName".equals(name)) { if (servletDef.getDisplayName() == null) { servletDef.setDisplayName(evp.getValue().stringifyValue()); } } else if ("largeIcon".equals(name)) { if (servletDef.getLargeIcon() == null) { servletDef.setLargeIcon(evp.getValue().stringifyValue()); } } else if ("smallIcon".equals(name)) { if (servletDef.getSmallIcon() == null) { servletDef.setSmallIcon(evp.getValue().stringifyValue()); } } else if ("asyncSupported".equals(name)) { if (servletDef.getAsyncSupported() == null) { servletDef.setAsyncSupported(evp.getValue() .stringifyValue()); } } else if ("loadOnStartup".equals(name)) { if (servletDef.getLoadOnStartup() == null) { servletDef .setLoadOnStartup(evp.getValue().stringifyValue()); } } else if ("initParams".equals(name)) { Map initParams = processAnnotationWebInitParams(evp .getValue()); if (isWebXMLservletDef) { Map webXMLInitParams = servletDef .getParameterMap(); for (Map.Entry entry : initParams .entrySet()) { if (webXMLInitParams.get(entry.getKey()) == null) { servletDef.addInitParameter(entry.getKey(), entry .getValue()); } } } else { for (Map.Entry entry : initParams .entrySet()) { servletDef.addInitParameter(entry.getKey(), entry .getValue()); } } } } if (!isWebXMLservletDef && urlPatterns != null) { fragment.addServlet(servletDef); } if (urlPatterns != null) { if (!fragment.getServletMappings().containsValue(servletName)) { for (String urlPattern : urlPatterns) { fragment.addServletMapping(urlPattern, servletName); } } } } /** * process filter annotation and merge with existing one! * FIXME: refactoring method too long and has redundant subroutines with * processAnnotationWebServlet! * @param className * @param ae * @param fragment */ protected void processAnnotationWebFilter(String className, AnnotationEntry ae, WebXml fragment) { String filterName = null; // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81 ElementValuePair[] evps = ae.getElementValuePairs(); for (ElementValuePair evp : evps) { String name = evp.getNameString(); if ("filterName".equals(name)) { filterName = evp.getValue().stringifyValue(); break; } } if (filterName == null) { // classname is default filterName as annotation has no name! filterName = className; } FilterDef filterDef = fragment.getFilters().get(filterName); FilterMap filterMap = new FilterMap(); boolean isWebXMLfilterDef; if (filterDef == null) { filterDef = new FilterDef(); filterDef.setFilterName(filterName); filterDef.setFilterClass(className); isWebXMLfilterDef = false; } else { isWebXMLfilterDef = true; } boolean urlPatternsSet = false; boolean dispatchTypesSet = false; String[] urlPatterns = null; for (ElementValuePair evp : evps) { String name = evp.getNameString(); if ("value".equals(name) || "urlPatterns".equals(name)) { if (urlPatternsSet) { throw new IllegalArgumentException(sm.getString( "contextConfig.urlPatternValue", className)); } urlPatterns = processAnnotationsStringArray(evp.getValue()); urlPatternsSet = urlPatterns.length > 0; for (String urlPattern : urlPatterns) { filterMap.addURLPattern(urlPattern); } } else if ("servletNames".equals(name)) { String[] servletNames = processAnnotationsStringArray(evp .getValue()); for (String servletName : servletNames) { filterMap.addServletName(servletName); } } else if ("dispatcherTypes".equals(name)) { String[] dispatcherTypes = processAnnotationsStringArray(evp .getValue()); dispatchTypesSet = dispatcherTypes.length > 0; for (String dispatcherType : dispatcherTypes) { filterMap.setDispatcher(dispatcherType); } } else if ("description".equals(name)) { if (filterDef.getDescription() == null) { filterDef.setDescription(evp.getValue().stringifyValue()); } } else if ("displayName".equals(name)) { if (filterDef.getDisplayName() == null) { filterDef.setDisplayName(evp.getValue().stringifyValue()); } } else if ("largeIcon".equals(name)) { if (filterDef.getLargeIcon() == null) { filterDef.setLargeIcon(evp.getValue().stringifyValue()); } } else if ("smallIcon".equals(name)) { if (filterDef.getSmallIcon() == null) { filterDef.setSmallIcon(evp.getValue().stringifyValue()); } } else if ("asyncSupported".equals(name)) { if (filterDef.getAsyncSupported() == null) { filterDef .setAsyncSupported(evp.getValue().stringifyValue()); } } else if ("initParams".equals(name)) { Map initParams = processAnnotationWebInitParams(evp .getValue()); if (isWebXMLfilterDef) { Map webXMLInitParams = filterDef .getParameterMap(); for (Map.Entry entry : initParams .entrySet()) { if (webXMLInitParams.get(entry.getKey()) == null) { filterDef.addInitParameter(entry.getKey(), entry .getValue()); } } } else { for (Map.Entry entry : initParams .entrySet()) { filterDef.addInitParameter(entry.getKey(), entry .getValue()); } } } } if (!isWebXMLfilterDef) { fragment.addFilter(filterDef); filterMap.setFilterName(filterName); fragment.addFilterMapping(filterMap); } if (urlPatternsSet || dispatchTypesSet) { Set fmap = fragment.getFilterMappings(); FilterMap descMap = null; for (FilterMap map : fmap) { if (filterName.equals(map.getFilterName())) { descMap = map; break; } } if (descMap != null) { String[] urlsPatterns = descMap.getURLPatterns(); if (urlPatternsSet && (urlsPatterns == null || urlsPatterns.length == 0)) { for (String urlPattern : filterMap.getURLPatterns()) { descMap.addURLPattern(urlPattern); } } String[] dispatcherNames = descMap.getDispatcherNames(); if (dispatchTypesSet && (dispatcherNames == null || dispatcherNames.length == 0)) { for (String dis : filterMap.getDispatcherNames()) { descMap.setDispatcher(dis); } } } } } protected String[] processAnnotationsStringArray(ElementValue ev) { ArrayList values = new ArrayList(); if (ev instanceof ArrayElementValue) { ElementValue[] arrayValues = ((ArrayElementValue) ev).getElementValuesArray(); for (ElementValue value : arrayValues) { values.add(value.stringifyValue()); } } else { values.add(ev.stringifyValue()); } String[] result = new String[values.size()]; return values.toArray(result); } protected Map processAnnotationWebInitParams( ElementValue ev) { Map result = new HashMap(); if (ev instanceof ArrayElementValue) { ElementValue[] arrayValues = ((ArrayElementValue) ev).getElementValuesArray(); for (ElementValue value : arrayValues) { if (value instanceof AnnotationElementValue) { ElementValuePair[] evps = ((AnnotationElementValue) value).getAnnotationEntry().getElementValuePairs(); String initParamName = null; String initParamValue = null; for (ElementValuePair evp : evps) { if ("name".equals(evp.getNameString())) { initParamName = evp.getValue().stringifyValue(); } else if ("value".equals(evp.getNameString())) { initParamValue = evp.getValue().stringifyValue(); } else { // Ignore } } result.put(initParamName, initParamValue); } } } return result; } private class FragmentJarScannerCallback implements JarScannerCallback { private static final String FRAGMENT_LOCATION = "META-INF/web-fragment.xml"; private final Map fragments = new HashMap(); @Override public void scan(JarURLConnection jarConn) throws IOException { URL url = jarConn.getURL(); URL resourceURL = jarConn.getJarFileURL(); Jar jar = null; InputStream is = null; WebXml fragment = new WebXml(); try { jar = JarFactory.newInstance(url); is = jar.getInputStream(FRAGMENT_LOCATION); if (is == null) { // If there is no web.xml, normal JAR no impact on // distributable fragment.setDistributable(true); } else { InputSource source = new InputSource( resourceURL.toString() + "!/" + FRAGMENT_LOCATION); source.setByteStream(is); parseWebXml(source, fragment, true); } } finally { if (is != null) { try { is.close(); } catch (IOException ioe) { // Ignore } } if (jar != null) { jar.close(); } fragment.setURL(url); if (fragment.getName() == null) { fragment.setName(fragment.getURL().toString()); } fragments.put(fragment.getName(), fragment); } } @Override public void scan(File file) throws IOException { InputStream stream = null; WebXml fragment = new WebXml(); try { File fragmentFile = new File(file, FRAGMENT_LOCATION); if (fragmentFile.isFile()) { stream = new FileInputStream(fragmentFile); InputSource source = new InputSource(fragmentFile.toURI().toURL().toString()); source.setByteStream(stream); parseWebXml(source, fragment, true); } } finally { if (stream != null) { try { stream.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } } fragment.setURL(file.toURI().toURL()); if (fragment.getName() == null) { fragment.setName(fragment.getURL().toString()); } fragments.put(fragment.getName(), fragment); } } public Map getFragments() { return fragments; } } private static class DefaultWebXmlCacheEntry { private final WebXml webXml; private final long globalTimeStamp; private final long hostTimeStamp; public DefaultWebXmlCacheEntry(WebXml webXml, long globalTimeStamp, long hostTimeStamp) { this.webXml = webXml; this.globalTimeStamp = globalTimeStamp; this.hostTimeStamp = hostTimeStamp; } public WebXml getWebXml() { return webXml; } public long getGlobalTimeStamp() { return globalTimeStamp; } public long getHostTimeStamp() { return hostTimeStamp; } } private static class JavaClassCacheEntry { private final JavaClass javaClass; private Set sciSet = null; public JavaClassCacheEntry(JavaClass javaClass) { this.javaClass = javaClass; } public JavaClass getJavaClass() { return javaClass; } public Set getSciSet() { return sciSet; } public void setSciSet(Set sciSet) { this.sciSet = sciSet; } } }