Author: markt Date: Mon Jan 30 11:14:06 2012 New Revision: 1237604 URL: http://svn.apache.org/viewvc?rev=1237604&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=52444 Remove need to load classes unless they actually match an HandlesTypes entry
Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java tomcat/trunk/java/org/apache/tomcat/util/bcel/classfile/JavaClass.java tomcat/trunk/test/org/apache/catalina/startup/TestContextConfigAnnotation.java 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=1237604&r1=1237603&r2=1237604&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java Mon Jan 30 11:14:06 2012 @@ -30,6 +30,7 @@ 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; @@ -183,14 +184,34 @@ public class ContextConfig implements Li * Map of ServletContainerInitializer to classes they expressed interest in. */ protected final Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap = - new LinkedHashMap<ServletContainerInitializer, Set<Class<?>>>(); + new LinkedHashMap<ServletContainerInitializer, Set<Class<?>>>(); /** * Map of Types to ServletContainerInitializer that are interested in those * types. */ protected final Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap = - new HashMap<Class<?>, Set<ServletContainerInitializer>>(); + new HashMap<Class<?>, Set<ServletContainerInitializer>>(); + + /** + * 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<String,JavaClassCacheEntry> javaClassCache = + new HashMap<String,JavaClassCacheEntry>(); + + /** + * 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 <code>Digester</code> we will use to process web application @@ -1196,6 +1217,9 @@ public class ContextConfig implements Li 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) { @@ -1473,6 +1497,11 @@ public class ContextConfig implements Li Class<?>[] types = ht.value(); if (types != null) { for (Class<?> type : types) { + if (type.isAnnotation()) { + handlesTypesAnnotations = true; + } else { + handlesTypesNonAnnotations = true; + } Set<ServletContainerInitializer> scis = typeInitializerMap.get(type); if (scis == null) { @@ -1993,59 +2022,180 @@ public class ContextConfig implements Li return; } - // No choice but to load the class 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<Class<?>> classes = initializerClassMap.get(sci); + if (classes == null) { + classes = new HashSet<Class<?>>(); + initializerClassMap.put(sci, classes); + } + classes.add(clazz); + } + } + } + + if (handlesTypesAnnotations) { + for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> 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); + 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<ServletContainerInitializer> result = + new HashSet<ServletContainerInitializer>(); + + 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<ServletContainerInitializer>()); + 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<ServletContainerInitializer> getSCIsForClass(String className) { + for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> 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; + return null; } catch (ClassNotFoundException e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); - return; + return null; } catch (ClassFormatError e) { log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), e); - return; + return null; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.debug(sm.getString("contextConfig.invalidSciHandlesTypes", className), t); - return; - } - - if (clazz.isAnnotation()) { - // Skip - return; - } - - boolean match = false; - - for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry : - typeInitializerMap.entrySet()) { - if (entry.getKey().isAnnotation()) { - AnnotationEntry[] annotationEntries = javaClass.getAnnotationEntries(); - for (AnnotationEntry annotationEntry : annotationEntries) { - if (entry.getKey().getName().equals( - getClassName(annotationEntry.getAnnotationType()))) { - match = true; - break; - } - } - } else if (entry.getKey().isAssignableFrom(clazz)) { - match = true; - } - if (match) { - for (ServletContainerInitializer sci : entry.getValue()) { - initializerClassMap.get(sci).add(clazz); - } - match = false; - } + return null; } + return clazz; } private static final String getClassName(String internalForm) { @@ -2455,4 +2605,25 @@ public class ContextConfig implements Li return hostTimeStamp; } } + + private static class JavaClassCacheEntry { + private final JavaClass javaClass; + private Set<ServletContainerInitializer> sciSet = null; + + public JavaClassCacheEntry(JavaClass javaClass) { + this.javaClass = javaClass; + } + + public JavaClass getJavaClass() { + return javaClass; + } + + public Set<ServletContainerInitializer> getSciSet() { + return sciSet; + } + + public void setSciSet(Set<ServletContainerInitializer> sciSet) { + this.sciSet = sciSet; + } + } } Modified: tomcat/trunk/java/org/apache/tomcat/util/bcel/classfile/JavaClass.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/bcel/classfile/JavaClass.java?rev=1237604&r1=1237603&r2=1237604&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/bcel/classfile/JavaClass.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/bcel/classfile/JavaClass.java Mon Jan 30 11:14:06 2012 @@ -180,6 +180,26 @@ public class JavaClass extends AccessFla /** + * @return Names of implemented interfaces. + */ + public String[] getInterfaceNames() { + return interface_names; + } + + + /** + * returns the super class name of this class. In the case that this class is + * java.lang.Object, it will return itself (java.lang.Object). This is probably incorrect + * but isn't fixed at this time to not break existing clients. + * + * @return Superclass name. + */ + public String getSuperclassName() { + return superclass_name; + } + + + /** * @return String representing class contents. */ @Override Modified: tomcat/trunk/test/org/apache/catalina/startup/TestContextConfigAnnotation.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/TestContextConfigAnnotation.java?rev=1237604&r1=1237603&r2=1237604&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/TestContextConfigAnnotation.java (original) +++ tomcat/trunk/test/org/apache/catalina/startup/TestContextConfigAnnotation.java Mon Jan 30 11:14:06 2012 @@ -267,6 +267,8 @@ public class TestContextConfigAnnotation @Test public void testCheckHandleTypes() throws Exception { ContextConfig config = new ContextConfig(); + config.handlesTypesAnnotations = true; + config.handlesTypesNonAnnotations = true; // Need a Context, Loader and ClassLoader for checkHandleTypes StandardContext context = new StandardContext(); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org