Author: markt Date: Fri Apr 23 13:34:17 2010 New Revision: 937288 URL: http://svn.apache.org/viewvc?rev=937288&view=rev Log: Servlet 3. Implement ServletContainerInitializer support
Modified: tomcat/trunk/java/org/apache/catalina/Context.java tomcat/trunk/java/org/apache/catalina/core/StandardContext.java tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties Modified: tomcat/trunk/java/org/apache/catalina/Context.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/Context.java?rev=937288&r1=937287&r2=937288&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/Context.java (original) +++ tomcat/trunk/java/org/apache/catalina/Context.java Fri Apr 23 13:34:17 2010 @@ -20,7 +20,9 @@ package org.apache.catalina; import java.net.URL; +import java.util.Set; +import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.descriptor.JspConfigDescriptor; @@ -1183,11 +1185,23 @@ public interface Context extends Contain */ public JspConfigDescriptor getJspConfigDescriptor(); + /** * Add a URL for a JAR that contains static resources in a * META-INF/resources directory that should be included in the static * resources for this context. */ public void addResourceJarUrl(URL url); + + + /** + * Add a ServletContainerInitializer instance to this web application. + * + * @param sci The instance to add + * @param classes The classes in which the initializer expressed an + * interest + */ + public void addServletContainerInitializer( + ServletContainerInitializer sci, Set<Class<?>> classes); } Modified: tomcat/trunk/java/org/apache/catalina/core/StandardContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/StandardContext.java?rev=937288&r1=937287&r2=937288&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/StandardContext.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/StandardContext.java Fri Apr 23 13:34:17 2010 @@ -28,8 +28,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Stack; import java.util.TreeMap; @@ -48,6 +50,7 @@ import javax.management.ObjectName; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.servlet.FilterConfig; +import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; @@ -229,6 +232,13 @@ public class StandardContext /** + * The ordered set of ServletContainerInitializers for this web application. + */ + private Map<ServletContainerInitializer,Set<Class<?>>> initializers = + new LinkedHashMap<ServletContainerInitializer,Set<Class<?>>>(); + + + /** * The set of application parameters defined for this application. */ private ApplicationParameter applicationParameters[] = @@ -988,6 +998,19 @@ public class StandardContext /** + * Add a ServletContainerInitializer instance to this web application. + * + * @param sci The instance to add + * @param classes The classes in which the initializer expressed an + * interest + */ + public void addServletContainerInitializer( + ServletContainerInitializer sci, Set<Class<?>> classes) { + initializers.put(sci, classes); + } + + + /** * Return the "follow standard delegation model" flag used to configure * our ClassLoader. */ @@ -4712,6 +4735,19 @@ public class StandardContext postWelcomeFiles(); } + // Call ServletContainerInitializers + for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : + initializers.entrySet()) { + try { + entry.getKey().onStartup(entry.getValue(), + getServletContext()); + } catch (ServletException e) { + // TODO: Log error + ok = false; + break; + } + } + // Configure and call application event listeners if (ok) { if (!listenerStart()) { @@ -4971,7 +5007,7 @@ public class StandardContext } - private void resetContext() throws Exception, MBeanRegistrationException { + private void resetContext() throws Exception { // Restore the original state ( pre reading web.xml in start ) // If you extend this - override this method and make sure to clean up children = new HashMap<String, Container>(); @@ -4987,6 +5023,8 @@ public class StandardContext applicationLifecycleListenersObjects = new Object[0]; jspConfigDescriptor = new ApplicationJspConfigDescriptor(); + initializers.clear(); + if(log.isDebugEnabled()) log.debug("resetContext " + oname); } Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java?rev=937288&r1=937287&r2=937288&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java Fri Apr 23 13:34:17 2010 @@ -19,11 +19,14 @@ 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; @@ -33,6 +36,7 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -40,7 +44,9 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; +import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; +import javax.servlet.annotation.HandlesTypes; import org.apache.tomcat.util.bcel.classfile.AnnotationElementValue; import org.apache.tomcat.util.bcel.classfile.AnnotationEntry; @@ -148,6 +154,19 @@ public class ContextConfig /** + * Map of ServletContainerInitializer to classes they expressed interest in. + */ + protected Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap = + new LinkedHashMap<ServletContainerInitializer, Set<Class<?>>>(); + + /** + * Map of Types to ServletContainerInitializer that are interested in those + * types. + */ + protected Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap = + new HashMap<Class<?>, Set<ServletContainerInitializer>>(); + + /** * The string resources for this package. */ protected static final StringManager sm = @@ -1199,51 +1218,204 @@ public class ContextConfig } if (webXmlVersion >= 3 && !webXml.isMetadataComplete()) { - // Process /WEB-INF/classes for annotations - URL webinfClasses; - try { - webinfClasses = - context.getServletContext().getResource("/WEB-INF/classes"); - processAnnotationsUrl(webinfClasses, webXml); - } catch (MalformedURLException e) { - log.error(sm.getString("contextConfig.webinfClassesUrl"), e); - } - - // Have to process JARs for fragments + // 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<String,WebXml> fragments = processJarsForWebFragments(); - - // Order the fragments + + // Step 2. Order the fragments. Set<WebXml> orderedFragments = WebXml.orderWebFragments(webXml, fragments); - // Process JARs for annotations - only need to process those + // Step 3. Look for ServletContainerInitializer implementations + ok = processServletContainerInitializers(orderedFragments); + + // Step 4. Process /WEB-INF/classes for annotations + // This will add any matching classes to the typeInitializerMap + if (ok) { + URL webinfClasses; + try { + webinfClasses = + context.getServletContext().getResource("/WEB-INF/classes"); + processAnnotationsUrl(webinfClasses, webXml); + } catch (MalformedURLException e) { + log.error(sm.getString("contextConfig.webinfClassesUrl"), e); + } + } + + // Step 5. Process JARs for annotations - only need to process those // fragments we are going to use - processAnnotations(orderedFragments); + // This will add any matching classes to the typeInitializerMap + if (ok) { + processAnnotations(orderedFragments); + } - // Merge fragment into application + // Step 6. Merge web-fragment.xml files into the main web.xml file. if (ok) { ok = webXml.merge(orderedFragments); } - // Apply merged web.xml to Context - webXml.configureContext(context); + // Step 7. Apply merged web.xml to Context + if (ok) { + webXml.configureContext(context); + + // Step 7a. 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(); + context.getServletContext().setAttribute( + org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, + mergedWebXml); + if (context.getLogEffectiveWebXml()) { + log.info("web.xml:\n" + mergedWebXml); + } + } - // Make the merged web.xml available to other components, - // specifically Jasper, to save those components from having to - // re-generate it. - String mergedWebXml = webXml.toXml(); - context.getServletContext().setAttribute( - org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, - mergedWebXml); - if (context.getLogEffectiveWebXml()) { - log.info("web.xml:\n" + mergedWebXml); + // Step 8. Look for static resources packaged in JARs + if (ok) { + processResourceJARs(orderedFragments); } - processResourceJARs(orderedFragments); + // Step 9. Apply the ServletContainerInitializer config to the + // context + if (ok) { + for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> 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.configureContext(context); - } + } + } + + + /** + * Scan JARs for ServletContainerInitializer implementations. + * Implementations will be added in web-fragment.xml priority order. + */ + protected boolean processServletContainerInitializers( + Set<WebXml> fragments) { + + for (WebXml fragment : fragments) { + URL jarUrl = fragment.getURL(); + JarFile jarFile = null; + InputStream is = null; + ServletContainerInitializer sci = null; + try { + JarURLConnection conn = + (JarURLConnection) jarUrl.openConnection(); + jarFile = conn.getJarFile(); + ZipEntry entry = jarFile.getEntry( + "META-INF/services/javax.servlet.ServletContainerInitializer"); + if (entry != null) { + is = jarFile.getInputStream(entry); + sci = getServletContainerInitializer(is); + } + } catch (IOException ioe) { + log.error(sm.getString( + "contextConfig.servletContainerInitializerFail", jarUrl, + context.getPath())); + return false; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Ignore + } + } + if (jarFile != null) { + try { + jarFile.close(); + } catch (IOException e) { + // Ignore + } + } + } + + if (sci == null) { + break; + } + + initializerClassMap.put(sci, new HashSet<Class<?>>()); + + HandlesTypes ht = + sci.getClass().getAnnotation(HandlesTypes.class); + if (ht != null) { + Class<?>[] types = ht.value(); + if (types != null) { + for (Class<?> type : types) { + Set<ServletContainerInitializer> scis = + typeInitializerMap.get(type.getCanonicalName()); + if (scis == null) { + scis = new HashSet<ServletContainerInitializer>(); + typeInitializerMap.put(type, scis); + } + scis.add(sci); + } + } + } + + } + return true; + } + + + /** + * 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; } @@ -1661,9 +1833,13 @@ public class ContextConfig ClassParser parser = new ClassParser(is, null); JavaClass clazz = parser.parse(); + + checkHandlesTypes(clazz); + String className = clazz.getClassName(); - AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries(); + AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries(); + for (AnnotationEntry ae : annotationsEntries) { String type = ae.getAnnotationType(); if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) { @@ -1678,6 +1854,39 @@ public class ContextConfig } } + /** + * For classes packaged with the web application, the class and each + * super class needs to be checked for a match with {...@link HandlesTypes}. + * @param javaClass + */ + protected void checkHandlesTypes(JavaClass javaClass) { + + // Skip this if we can + if (typeInitializerMap.size() == 0) + return; + + // No choice but to load the class + String className = javaClass.getClassName(); + + Class<?> clazz = null; + try { + clazz = Class.forName(className, true, + context.getLoader().getClassLoader()); + } catch (ClassNotFoundException e) { + log.warn(sm.getString("contextConfig.invalidSciHandlesTypes", + className), e); + } + + for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry : + typeInitializerMap.entrySet()) { + if (entry.getKey().isAssignableFrom(clazz)) { + for (ServletContainerInitializer sci : entry.getValue()) { + initializerClassMap.get(sci).add(clazz); + } + } + } + } + protected void processAnnotationWebServlet(String className, AnnotationEntry ae, WebXml fragment) { if (fragment.getServlets().containsKey(className)) { Modified: tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties?rev=937288&r1=937287&r2=937288&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties Fri Apr 23 13:34:17 2010 @@ -41,6 +41,8 @@ contextConfig.init=ContextConfig: Initia contextConfig.inputStreamFile=Unable to process file [{0}] for annotations contextConfig.inputStreamJar=Unable to process Jar entry [{0}] from Jar [{1}] for annotations contextConfig.inputStreamJndi=Unable to process resource element [{0}] for annotations +contextConfig.invalidSci=The ServletContentInitializer [{0}] could not be created +contextConfig.invalidSciHandlesTypes=Unable to load class [{0}] to check against the @HandlesTypes annotation of one or more ServletContentInitializers. contextConfig.jarUrl=The connection created for URL [{0}] was not a JarUrlConnection contextConfig.jar=Unable to process resource [{0}] for annotations contextConfig.jndiUrl=Unable to process JNDI URL [{0}] for annotations --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org