Author: markt Date: Thu Jan 10 11:39:46 2013 New Revision: 1431293 URL: http://svn.apache.org/viewvc?rev=1431293&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=54379 Implement support for post-construct and pre-destroy elements in web.xml Patch by Violeta Georgieva.
Added: tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java (with props) tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml (with props) tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml (with props) Modified: tomcat/trunk/java/org/apache/catalina/Context.java tomcat/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties tomcat/trunk/java/org/apache/catalina/core/StandardContext.java tomcat/trunk/java/org/apache/catalina/deploy/WebXml.java tomcat/trunk/java/org/apache/catalina/startup/FailedContext.java tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties tomcat/trunk/java/org/apache/catalina/startup/WebRuleSet.java tomcat/trunk/java/org/apache/catalina/util/Introspection.java tomcat/trunk/test/org/apache/catalina/core/TestStandardContext.java tomcat/trunk/test/org/apache/catalina/core/TesterContext.java tomcat/trunk/test/org/apache/catalina/deploy/TestWebXml.java tomcat/trunk/test/org/apache/catalina/startup/TestContextConfig.java tomcat/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java tomcat/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml Modified: tomcat/trunk/java/org/apache/catalina/Context.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/Context.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/Context.java (original) +++ tomcat/trunk/java/org/apache/catalina/Context.java Thu Jan 10 11:39:46 2013 @@ -18,6 +18,7 @@ package org.apache.catalina; import java.net.URL; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.servlet.ServletContainerInitializer; @@ -1420,5 +1421,101 @@ public interface Context extends Contain * JAR. */ public boolean getAddWebinfClassesResources(); -} + /** + * Add a post construct method definition for the given class, if there is + * an existing definition for the specified class - IllegalArgumentException + * will be thrown. + * + * @param clazz Fully qualified class name + * @param method + * Post construct method name + * @throws IllegalArgumentException + * if the fully qualified class name or method name are + * <code>NULL</code>; if there is already post construct method + * definition for the given class + */ + public void addPostConstructMethod(String clazz, String method); + + /** + * Add a pre destroy method definition for the given class, if there is an + * existing definition for the specified class - IllegalArgumentException + * will be thrown. + * + * @param clazz Fully qualified class name + * @param method + * Post construct method name + * @throws IllegalArgumentException + * if the fully qualified class name or method name are + * <code>NULL</code>; if there is already pre destroy method + * definition for the given class + */ + public void addPreDestroyMethod(String clazz, String method); + + /** + * Removes the post construct method definition for the given class, if it + * exists; otherwise, no action is taken. + * + * @param clazz + * Fully qualified class name + */ + public void removePostConstructMethod(String clazz); + + /** + * Removes the pre destroy method definition for the given class, if it + * exists; otherwise, no action is taken. + * + * @param clazz + * Fully qualified class name + */ + public void removePreDestroyMethod(String clazz); + + /** + * Returns the method name that is specified as post construct method for + * the given class, if it exists; otherwise <code>NULL</code> will be + * returned. + * + * @param clazz + * Fully qualified class name + * + * @return the method name that is specified as post construct method for + * the given class, if it exists; otherwise <code>NULL</code> will + * be returned. + */ + public String findPostConstructMethod(String clazz); + + /** + * Returns the method name that is specified as pre destroy method for the + * given class, if it exists; otherwise <code>NULL</code> will be returned. + * + * @param clazz + * Fully qualified class name + * + * @return the method name that is specified as pre destroy method for the + * given class, if it exists; otherwise <code>NULL</code> will be + * returned. + */ + public String findPreDestroyMethod(String clazz); + + /** + * Returns a map with keys - fully qualified class names of the classes that + * have post construct methods and the values are the corresponding method + * names. If there are no such classes an empty map will be returned. + * + * @return a map with keys - fully qualified class names of the classes that + * have post construct methods and the values are the corresponding + * method names. + */ + public Map<String, String> findPostConstructMethods(); + + /** + * Returns a map with keys - fully qualified class names of the classes that + * have pre destroy methods and the values are the corresponding method + * names. If there are no such classes an empty map will be returned. + * + * @return a map with keys - fully qualified class names of the classes that + * have pre destroy methods and the values are the corresponding + * method names. + */ + public Map<String, String> findPreDestroyMethods(); +} Modified: tomcat/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java Thu Jan 10 11:39:46 2013 @@ -18,10 +18,10 @@ package org.apache.catalina.core; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; @@ -79,6 +79,8 @@ public class DefaultInstanceManager impl private final Properties restrictedServlets = new Properties(); private final Map<Class<?>, AnnotationCacheEntry[]> annotationCache = new WeakHashMap<>(); + private final Map<String, String> postConstructMethods; + private final Map<String, String> preDestroyMethods; public DefaultInstanceManager(Context context, Map<String, Map<String, String>> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) { classLoader = catalinaContext.getLoader().getClassLoader(); @@ -125,6 +127,8 @@ public class DefaultInstanceManager impl } this.context = context; this.injectionMap = injectionMap; + this.postConstructMethods = catalinaContext.findPostConstructMethods(); + this.preDestroyMethods = catalinaContext.findPreDestroyMethods(); } @Override @@ -331,7 +335,9 @@ public class DefaultInstanceManager impl // Initialize methods annotations Method[] methods = Introspection.getDeclaredMethods(clazz); Method postConstruct = null; + String postConstructFromXml = postConstructMethods.get(clazz.getName()); Method preDestroy = null; + String preDestroyFromXml = preDestroyMethods.get(clazz.getName()); for (Method method : methods) { if (context != null) { // Resource injection only if JNDI is enabled @@ -388,41 +394,30 @@ public class DefaultInstanceManager impl } } - if (method.isAnnotationPresent(PostConstruct.class)) { - if ((postConstruct != null) || - (method.getParameterTypes().length != 0) || - (Modifier.isStatic(method.getModifiers())) || - (method.getExceptionTypes().length > 0) || - (!method.getReturnType().getName().equals("void"))) { - throw new IllegalArgumentException( - "Invalid PostConstruct annotation"); - } - postConstruct = method; - } + postConstruct = findPostConstruct(postConstruct, postConstructFromXml, method); - if (method.isAnnotationPresent(PreDestroy.class)) { - if ((preDestroy != null || - method.getParameterTypes().length != 0) || - (Modifier.isStatic(method.getModifiers())) || - (method.getExceptionTypes().length > 0) || - (!method.getReturnType().getName().equals("void"))) { - throw new IllegalArgumentException( - "Invalid PreDestroy annotation"); - } - preDestroy = method; - } + preDestroy = findPreDestroy(preDestroy, preDestroyFromXml, method); } + if (postConstruct != null) { annotations.add(new AnnotationCacheEntry( postConstruct.getName(), postConstruct.getParameterTypes(), null, AnnotationCacheEntryType.POST_CONSTRUCT)); + } else if (postConstructFromXml != null) { + throw new IllegalArgumentException("Post construct method " + + postConstructFromXml + " for class " + clazz.getName() + + " is declared in deployment descriptor but cannot be found."); } if (preDestroy != null) { annotations.add(new AnnotationCacheEntry( preDestroy.getName(), preDestroy.getParameterTypes(), null, AnnotationCacheEntryType.PRE_DESTROY)); + } else if (preDestroyFromXml != null) { + throw new IllegalArgumentException("Pre destroy method " + + preDestroyFromXml + " for class " + clazz.getName() + + " is declared in deployment descriptor but cannot be found."); } if (annotations.isEmpty()) { // Use common object to save memory @@ -703,6 +698,44 @@ public class DefaultInstanceManager impl return result; } + + private static Method findPostConstruct(Method currentPostConstruct, + String postConstructFromXml, Method method) { + return findLifecycleCallback(currentPostConstruct, + postConstructFromXml, method, PostConstruct.class); + } + + private static Method findPreDestroy(Method currentPreDestroy, + String preDestroyFromXml, Method method) { + return findLifecycleCallback(currentPreDestroy, + preDestroyFromXml, method, PreDestroy.class); + } + + private static Method findLifecycleCallback(Method currentMethod, + String methodNameFromXml, Method method, + Class<? extends Annotation> annotation) { + Method result = currentMethod; + if (methodNameFromXml != null) { + if (method.getName().equals(methodNameFromXml)) { + if (!Introspection.isValidLifecycleCallback(method)) { + throw new IllegalArgumentException( + "Invalid " + annotation.getName() + " annotation"); + } + result = method; + } + } else { + if (method.isAnnotationPresent(annotation)) { + if (currentMethod != null || + !Introspection.isValidLifecycleCallback(method)) { + throw new IllegalArgumentException( + "Invalid " + annotation.getName() + " annotation"); + } + result = method; + } + } + return result; + } + private static final class AnnotationCacheEntry { private final String accessibleObjectName; private final Class<?>[] paramTypes; Modified: tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties Thu Jan 10 11:39:46 2013 @@ -119,6 +119,10 @@ standardContext.notWrapper=Child of a Co standardContext.parameter.duplicate=Duplicate context initialization parameter {0} standardContext.parameter.required=Both parameter name and parameter value are required standardContext.pathInvalid=A context path must either be an empty string or start with a ''/''. The path [{0}] does not meet these criteria and has been changed to [{1}] +standardContext.postconstruct.duplicate=Duplicate post construct method definition for class {0} +standardContext.postconstruct.required=Both fully qualified class name and method name are required +standardContext.predestroy.duplicate=Duplicate pre destroy method definition for class {0} +standardContext.predestroy.required=Both fully qualified class name and method name are required standardContext.reloadingCompleted=Reloading Context with name [{0}] is completed standardContext.reloadingStarted=Reloading Context with name [{0}] has started standardContext.resourcesStart=Error starting static Resources 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=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/StandardContext.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/StandardContext.java Thu Jan 10 11:39:46 2013 @@ -823,6 +823,10 @@ public class StandardContext extends Con private boolean jndiExceptionOnFailedWrite = true; + private Map<String, String> postConstructMethods = new HashMap<>(); + private Map<String, String> preDestroyMethods = new HashMap<>(); + + // ----------------------------------------------------- Context Properties @Override @@ -5844,6 +5848,72 @@ public class StandardContext extends Con } + @Override + public void addPostConstructMethod(String clazz, String method) { + if (clazz == null || method == null) + throw new IllegalArgumentException( + sm.getString("standardContext.postconstruct.required")); + if (postConstructMethods.get(clazz) != null) + throw new IllegalArgumentException(sm.getString( + "standardContext.postconstruct.duplicate", clazz)); + + postConstructMethods.put(clazz, method); + fireContainerEvent("addPostConstructMethod", clazz); + } + + + @Override + public void removePostConstructMethod(String clazz) { + postConstructMethods.remove(clazz); + fireContainerEvent("removePostConstructMethod", clazz); + } + + + @Override + public void addPreDestroyMethod(String clazz, String method) { + if (clazz == null || method == null) + throw new IllegalArgumentException( + sm.getString("standardContext.predestroy.required")); + if (preDestroyMethods.get(clazz) != null) + throw new IllegalArgumentException(sm.getString( + "standardContext.predestroy.duplicate", clazz)); + + preDestroyMethods.put(clazz, method); + fireContainerEvent("addPreDestroyMethod", clazz); + } + + + @Override + public void removePreDestroyMethod(String clazz) { + preDestroyMethods.remove(clazz); + fireContainerEvent("removePreDestroyMethod", clazz); + } + + + @Override + public String findPostConstructMethod(String clazz) { + return postConstructMethods.get(clazz); + } + + + @Override + public String findPreDestroyMethod(String clazz) { + return preDestroyMethods.get(clazz); + } + + + @Override + public Map<String, String> findPostConstructMethods() { + return postConstructMethods; + } + + + @Override + public Map<String, String> findPreDestroyMethods() { + return preDestroyMethods; + } + + /** * Set the appropriate context attribute for our work directory. */ Modified: tomcat/trunk/java/org/apache/catalina/deploy/WebXml.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/deploy/WebXml.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/deploy/WebXml.java (original) +++ tomcat/trunk/java/org/apache/catalina/deploy/WebXml.java Thu Jan 10 11:39:46 2013 @@ -559,6 +559,27 @@ public class WebXml { return localeEncodingMappings; } + // post-construct elements + private Map<String, String> postConstructMethods = new HashMap<>(); + public void addPostConstructMethods(String clazz, String method) { + if (!postConstructMethods.containsKey(clazz)) { + postConstructMethods.put(clazz, method); + } + } + public Map<String, String> getPostConstructMethods() { + return postConstructMethods; + } + + // pre-destroy elements + private Map<String, String> preDestroyMethods = new HashMap<>(); + public void addPreDestroyMethods(String clazz, String method) { + if (!preDestroyMethods.containsKey(clazz)) { + preDestroyMethods.put(clazz, method); + } + } + public Map<String, String> getPreDestroyMethods() { + return preDestroyMethods; + } // Attributes not defined in web.xml or web-fragment.xml @@ -1066,6 +1087,32 @@ public class WebXml { } sb.append('\n'); + if (!postConstructMethods.isEmpty()) { + for (Entry<String, String> entry : postConstructMethods + .entrySet()) { + sb.append(" <post-construct>\n"); + appendElement(sb, INDENT4, "lifecycle-callback-class", + entry.getKey()); + appendElement(sb, INDENT4, "lifecycle-callback-method", + entry.getValue()); + sb.append(" </post-construct>\n"); + } + sb.append('\n'); + } + + if (!preDestroyMethods.isEmpty()) { + for (Entry<String, String> entry : preDestroyMethods + .entrySet()) { + sb.append(" <pre-destroy>\n"); + appendElement(sb, INDENT4, "lifecycle-callback-class", + entry.getKey()); + appendElement(sb, INDENT4, "lifecycle-callback-method", + entry.getValue()); + sb.append(" </pre-destroy>\n"); + } + sb.append('\n'); + } + for (MessageDestinationRef mdr : messageDestinationRefs.values()) { sb.append(" <message-destination-ref>\n"); appendElement(sb, INDENT4, "description", mdr.getDescription()); @@ -1365,6 +1412,14 @@ public class WebXml { } } } + + for (Entry<String, String> entry : postConstructMethods.entrySet()) { + context.addPostConstructMethod(entry.getKey(), entry.getValue()); + } + + for (Entry<String, String> entry : preDestroyMethods.entrySet()) { + context.addPreDestroyMethod(entry.getKey(), entry.getValue()); + } } /** @@ -1860,6 +1915,28 @@ public class WebXml { } } + if (postConstructMethods.isEmpty()) { + for (WebXml fragment : fragments) { + if (!mergeLifecycleCallback(fragment.getPostConstructMethods(), + temp.getPostConstructMethods(), fragment, + "Post Construct Methods")) { + return false; + } + } + postConstructMethods.putAll(temp.getPostConstructMethods()); + } + + if (preDestroyMethods.isEmpty()) { + for (WebXml fragment : fragments) { + if (!mergeLifecycleCallback(fragment.getPreDestroyMethods(), + temp.getPreDestroyMethods(), fragment, + "Pre Destroy Methods")) { + return false; + } + } + preDestroyMethods.putAll(temp.getPreDestroyMethods()); + } + return true; } @@ -2084,6 +2161,26 @@ public class WebXml { } + private static <T> boolean mergeLifecycleCallback( + Map<String, String> fragmentMap, Map<String, String> tempMap, + WebXml fragment, String mapName) { + for (Entry<String, String> entry : fragmentMap.entrySet()) { + final String key = entry.getKey(); + final String value = entry.getValue(); + if (tempMap.containsKey(key)) { + if (value != null && !value.equals(tempMap.get(key))) { + log.error(sm.getString("webXml.mergeConflictString", + mapName, key, fragment.getName(), fragment.getURL())); + return false; + } + } else { + tempMap.put(key, value); + } + } + return true; + } + + /** * Generates the sub-set of the web-fragment.xml files to be processed in * the order that the fragments must be processed as per the rules in the Modified: tomcat/trunk/java/org/apache/catalina/startup/FailedContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/FailedContext.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/FailedContext.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/FailedContext.java Thu Jan 10 11:39:46 2013 @@ -20,6 +20,7 @@ import java.beans.PropertyChangeListener import java.io.File; import java.net.URL; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.servlet.ServletContainerInitializer; @@ -681,4 +682,28 @@ public class FailedContext extends Lifec } @Override public boolean getAddWebinfClassesResources() { return false; } + + @Override + public void addPostConstructMethod(String clazz, String method) { /* NO-OP */ } + + @Override + public void addPreDestroyMethod(String clazz, String method) { /* NO-OP */ } + + @Override + public void removePostConstructMethod(String clazz) { /* NO-OP */ } + + @Override + public void removePreDestroyMethod(String clazz) { /* NO-OP */ } + + @Override + public String findPostConstructMethod(String clazz) { return null; } + + @Override + public String findPreDestroyMethod(String clazz) { return null; } + + @Override + public Map<String, String> findPostConstructMethods() { return null; } + + @Override + public Map<String, String> findPreDestroyMethods() { return null; } } \ No newline at end of file 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=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties Thu Jan 10 11:39:46 2013 @@ -130,6 +130,8 @@ webAnnotationSet.invalidInjection=Invali webRuleSet.absoluteOrdering=<absolute-ordering> element not valid in web-fragment.xml and will be ignored webRuleSet.absoluteOrderingCount=<absolute-ordering> element is limited to 1 occurrence webRuleSet.nameCount=<name> element is limited to 1 occurrence +webRuleSet.postconstruct.duplicate=Duplicate post construct method definition for class {0} +webRuleSet.predestroy.duplicate=Duplicate pre destroy method definition for class {0} webRuleSet.relativeOrdering=<ordering> element not valid in web.xml and will be ignored webRuleSet.relativeOrderingCount=<ordering> element is limited to 1 occurrence xmlErrorHandler.error=Non-fatal error [{0}] reported processing [{1}]. Modified: tomcat/trunk/java/org/apache/catalina/startup/WebRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/WebRuleSet.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/WebRuleSet.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/WebRuleSet.java Thu Jan 10 11:39:46 2013 @@ -473,6 +473,15 @@ public class WebRuleSet extends RuleSetB digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/locale", 0); digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/encoding", 1); + digester.addRule(fullPrefix + "/post-construct", + new LifecycleCallbackRule("addPostConstructMethods", 2, true)); + digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-class", 0); + digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-method", 1); + + digester.addRule(fullPrefix + "/pre-destroy", + new LifecycleCallbackRule("addPreDestroyMethods", 2, false)); + digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-class", 0); + digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-method", 1); } protected void configureNamingRules(Digester digester) { @@ -1293,4 +1302,39 @@ final class MappedNameRule extends Rule ResourceBase resourceBase = (ResourceBase) digester.peek(); resourceBase.setProperty("mappedName", text.trim()); } -} \ No newline at end of file +} + +/** + * A rule that fails if more than one post construct or pre destroy methods + * are configured per class. + */ +final class LifecycleCallbackRule extends CallMethodRule { + + private final boolean postConstruct; + + public LifecycleCallbackRule(String methodName, int paramCount, + boolean postConstruct) { + super(methodName, paramCount); + this.postConstruct = postConstruct; + } + + @Override + public void end(String namespace, String name) throws Exception { + Object[] params = (Object[]) digester.peekParams(); + if (params != null && params.length == 2) { + WebXml webXml = (WebXml) digester.peek(); + if (postConstruct) { + if (webXml.getPostConstructMethods().containsKey(params[0])) { + throw new IllegalArgumentException(WebRuleSet.sm.getString( + "webRuleSet.postconstruct.duplicate", params[0])); + } + } else { + if (webXml.getPreDestroyMethods().containsKey(params[0])) { + throw new IllegalArgumentException(WebRuleSet.sm.getString( + "webRuleSet.predestroy.duplicate", params[0])); + } + } + } + super.end(namespace, name); + } +} Modified: tomcat/trunk/java/org/apache/catalina/util/Introspection.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/util/Introspection.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/util/Introspection.java (original) +++ tomcat/trunk/java/org/apache/catalina/util/Introspection.java Thu Jan 10 11:39:46 2013 @@ -19,6 +19,7 @@ package org.apache.catalina.util; import java.beans.Introspector; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; @@ -68,6 +69,24 @@ public class Introspection { return false; } + /** + * Determines if a method is a valid lifecycle callback method. + * + * @param method + * The method to test + * + * @return <code>true</code> if the method is a valid lifecycle callback + * method, else <code>false</code> + */ + public static boolean isValidLifecycleCallback(Method method) { + if (method.getParameterTypes().length != 0 + || Modifier.isStatic(method.getModifiers()) + || method.getExceptionTypes().length > 0 + || !method.getReturnType().getName().equals("void")) { + return false; + } + return true; + } /** * Obtain the declared fields for a class taking account of any security Modified: tomcat/trunk/test/org/apache/catalina/core/TestStandardContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/core/TestStandardContext.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/core/TestStandardContext.java (original) +++ tomcat/trunk/test/org/apache/catalina/core/TestStandardContext.java Thu Jan 10 11:39:46 2013 @@ -754,4 +754,38 @@ public class TestStandardContext extends return false; // Don't care } } + + @Test(expected = IllegalArgumentException.class) + public void testAddPostConstructMethodNullClassName() { + new StandardContext().addPostConstructMethod(null, ""); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddPostConstructMethodNullMethodName() { + new StandardContext().addPostConstructMethod("", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddPostConstructMethodConflicts() { + StandardContext standardContext = new StandardContext(); + standardContext.addPostConstructMethod("a", "a"); + standardContext.addPostConstructMethod("a", "b"); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddPreDestroyMethodNullClassName() { + new StandardContext().addPreDestroyMethod(null, ""); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddPreDestroyMethodNullMethodName() { + new StandardContext().addPreDestroyMethod("", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddPreDestroyMethodConflicts() { + StandardContext standardContext = new StandardContext(); + standardContext.addPreDestroyMethod("a", "a"); + standardContext.addPreDestroyMethod("a", "b"); + } } Modified: tomcat/trunk/test/org/apache/catalina/core/TesterContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/core/TesterContext.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/core/TesterContext.java (original) +++ tomcat/trunk/test/org/apache/catalina/core/TesterContext.java Thu Jan 10 11:39:46 2013 @@ -20,6 +20,7 @@ import java.beans.PropertyChangeListener import java.io.File; import java.net.URL; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.management.ObjectName; @@ -1112,4 +1113,44 @@ public class TesterContext implements Co public boolean getAddWebinfClassesResources() { return false; } + + @Override + public void addPostConstructMethod(String clazz, String method) { + // NO-OP + } + + @Override + public void addPreDestroyMethod(String clazz, String method) { + // NO-OP + } + + @Override + public void removePostConstructMethod(String clazz) { + // NO-OP + } + + @Override + public void removePreDestroyMethod(String clazz) { + // NO-OP + } + + @Override + public String findPostConstructMethod(String clazz) { + return null; + } + + @Override + public String findPreDestroyMethod(String clazz) { + return null; + } + + @Override + public Map<String,String> findPostConstructMethods() { + return null; + } + + @Override + public Map<String,String> findPreDestroyMethods() { + return null; + } } Modified: tomcat/trunk/test/org/apache/catalina/deploy/TestWebXml.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/deploy/TestWebXml.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/deploy/TestWebXml.java (original) +++ tomcat/trunk/test/org/apache/catalina/deploy/TestWebXml.java Thu Jan 10 11:39:46 2013 @@ -17,8 +17,11 @@ package org.apache.catalina.deploy; -import static org.junit.Assert.assertEquals; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.junit.Assert; import org.junit.Test; /** @@ -32,46 +35,46 @@ public class TestWebXml { WebXml webxml = new WebXml(); // Defaults - assertEquals(3, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals(3, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); // Both get changed webxml.setVersion("2.5"); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(5, webxml.getMinorVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(5, webxml.getMinorVersion()); // Reset webxml.setVersion("0.0"); - assertEquals(0, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals(0, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); // null input should be ignored webxml.setVersion(null); - assertEquals(0, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals(0, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); // major only webxml.setVersion("3"); - assertEquals(3, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals(3, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); // no minor digit webxml.setVersion("0.0"); // reset webxml.setVersion("3."); - assertEquals(3, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals(3, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); // minor only webxml.setVersion("0.0"); // reset webxml.setVersion(".5"); - assertEquals(0, webxml.getMajorVersion()); - assertEquals(5, webxml.getMinorVersion()); + Assert.assertEquals(0, webxml.getMajorVersion()); + Assert.assertEquals(5, webxml.getMinorVersion()); // leading & training zeros webxml.setVersion("0.0"); // reset webxml.setVersion("002.500"); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(500, webxml.getMinorVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(500, webxml.getMinorVersion()); } @Test @@ -81,9 +84,9 @@ public class TestWebXml { webxml.setPublicId( org.apache.catalina.startup.Constants.WebDtdPublicId_22); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(2, webxml.getMinorVersion()); - assertEquals("2.2", webxml.getVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(2, webxml.getMinorVersion()); + Assert.assertEquals("2.2", webxml.getVersion()); } @Test @@ -93,9 +96,9 @@ public class TestWebXml { webxml.setPublicId( org.apache.catalina.startup.Constants.WebDtdPublicId_23); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(3, webxml.getMinorVersion()); - assertEquals("2.3", webxml.getVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(3, webxml.getMinorVersion()); + Assert.assertEquals("2.3", webxml.getVersion()); } @Test @@ -105,9 +108,9 @@ public class TestWebXml { webxml.setPublicId( org.apache.catalina.startup.Constants.WebSchemaPublicId_24); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(4, webxml.getMinorVersion()); - assertEquals("2.4", webxml.getVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(4, webxml.getMinorVersion()); + Assert.assertEquals("2.4", webxml.getVersion()); } @Test @@ -117,9 +120,9 @@ public class TestWebXml { webxml.setPublicId( org.apache.catalina.startup.Constants.WebSchemaPublicId_25); - assertEquals(2, webxml.getMajorVersion()); - assertEquals(5, webxml.getMinorVersion()); - assertEquals("2.5", webxml.getVersion()); + Assert.assertEquals(2, webxml.getMajorVersion()); + Assert.assertEquals(5, webxml.getMinorVersion()); + Assert.assertEquals("2.5", webxml.getVersion()); } @Test @@ -129,8 +132,91 @@ public class TestWebXml { webxml.setPublicId( org.apache.catalina.startup.Constants.WebSchemaPublicId_30); - assertEquals(3, webxml.getMajorVersion()); - assertEquals(0, webxml.getMinorVersion()); - assertEquals("3.0", webxml.getVersion()); + Assert.assertEquals(3, webxml.getMajorVersion()); + Assert.assertEquals(0, webxml.getMinorVersion()); + Assert.assertEquals("3.0", webxml.getVersion()); + } + + @Test + public void testLifecycleMethodsWebXml() { + WebXml webxml = new WebXml(); + webxml.addPostConstructMethods("a", "a"); + webxml.addPreDestroyMethods("b", "b"); + + WebXml fragment = new WebXml(); + fragment.addPostConstructMethods("c", "c"); + fragment.addPreDestroyMethods("d", "d"); + + Set<WebXml> fragments = new HashSet<>(); + fragments.add(fragment); + + webxml.merge(fragments); + + Map<String, String> postConstructMethods = webxml.getPostConstructMethods(); + Map<String, String> preDestroyMethods = webxml.getPreDestroyMethods(); + Assert.assertEquals(1, postConstructMethods.size()); + Assert.assertEquals(1, preDestroyMethods.size()); + + Assert.assertEquals("a", postConstructMethods.get("a")); + Assert.assertEquals("b", preDestroyMethods.get("b")); + } + + @Test + public void testLifecycleMethodsWebFragments() { + WebXml webxml = new WebXml(); + + WebXml fragment1 = new WebXml(); + fragment1.addPostConstructMethods("a", "a"); + fragment1.addPreDestroyMethods("b", "b"); + + WebXml fragment2 = new WebXml(); + fragment2.addPostConstructMethods("c", "c"); + fragment2.addPreDestroyMethods("d", "d"); + + Set<WebXml> fragments = new HashSet<>(); + fragments.add(fragment1); + fragments.add(fragment2); + + webxml.merge(fragments); + + Map<String, String> postConstructMethods = webxml.getPostConstructMethods(); + Map<String, String> preDestroyMethods = webxml.getPreDestroyMethods(); + Assert.assertEquals(2, postConstructMethods.size()); + Assert.assertEquals(2, preDestroyMethods.size()); + + Assert.assertEquals("a", postConstructMethods.get("a")); + Assert.assertEquals("c", postConstructMethods.get("c")); + Assert.assertEquals("b", preDestroyMethods.get("b")); + Assert.assertEquals("d", preDestroyMethods.get("d")); + } + + @Test + public void testLifecycleMethodsWebFragmentsWithConflicts() { + WebXml webxml = new WebXml(); + + WebXml fragment1 = new WebXml(); + fragment1.addPostConstructMethods("a", "a"); + fragment1.addPreDestroyMethods("b", "a"); + + WebXml fragment2 = new WebXml(); + fragment2.addPostConstructMethods("a", "b"); + + Set<WebXml> fragments = new HashSet<>(); + fragments.add(fragment1); + fragments.add(fragment2); + + Assert.assertFalse(webxml.merge(fragments)); + + Assert.assertEquals(0, webxml.getPostConstructMethods().size()); + + WebXml fragment3 = new WebXml(); + fragment3.addPreDestroyMethods("b", "b"); + + fragments.remove(fragment2); + fragments.add(fragment3); + + Assert.assertFalse(webxml.merge(fragments)); + + Assert.assertEquals(0, webxml.getPreDestroyMethods().size()); } } Modified: tomcat/trunk/test/org/apache/catalina/startup/TestContextConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/TestContextConfig.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/TestContextConfig.java (original) +++ tomcat/trunk/test/org/apache/catalina/startup/TestContextConfig.java Thu Jan 10 11:39:46 2013 @@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRes import org.junit.Assert; import org.junit.Test; +import org.apache.catalina.Context; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.buf.ByteChunk; @@ -106,6 +107,25 @@ public class TestContextConfig extends T null, HttpServletResponse.SC_NOT_FOUND); } + @Test + public void testBug54379() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + File appDir = new File("test/webapp-3.0-fragments"); + Context context = + tomcat.addWebapp(null, "/test", appDir.getAbsolutePath()); + + Tomcat.addServlet(context, "TestServlet", + "org.apache.catalina.startup.TesterServletWithLifeCycleMethods"); + context.addServletMapping("/testServlet", "TestServlet"); + + tomcat.enableNaming(); + + tomcat.start(); + + assertPageContains("/test/testServlet", "postConstruct1()"); + } + private static class CustomDefaultServletSCI implements ServletContainerInitializer { Modified: tomcat/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java (original) +++ tomcat/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java Thu Jan 10 11:39:46 2013 @@ -115,6 +115,13 @@ public class TestWebRuleSet { parse(new WebXml(), "web-1ordering.xml", false, true); } + @Test + public void testLifecycleMethodsDefinitions() throws Exception { + // post-construct and pre-destroy + parse(new WebXml(), "web-1lifecyclecallback.xml", false, true); + // conflicting post-construct definitions + parse(new WebXml(), "web-2lifecyclecallback.xml", false, false); + } private synchronized void parse(WebXml webXml, String target, boolean fragment, boolean expected) throws FileNotFoundException { Added: tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java?rev=1431293&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java (added) +++ tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java Thu Jan 10 11:39:46 2013 @@ -0,0 +1,59 @@ +/* + * 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.IOException; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TesterServletWithLifeCycleMethods extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private String result; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.getWriter().print(result); + } + + @PostConstruct + protected void postConstruct() { + result = "postConstruct()"; + } + + @PreDestroy + protected void preDestroy() { + result = "preDestroy()"; + } + + protected void postConstruct1() { + result = "postConstruct1()"; + } + + protected void preDestroy1() { + result = "preDestroy1()"; + } +} Propchange: tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java ------------------------------------------------------------------------------ svn:eol-style = native Added: tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml?rev=1431293&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml (added) +++ tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml Thu Jan 10 11:39:46 2013 @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<web-app xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee + http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + version="3.0" + metadata-complete="true"> + <post-construct> + <lifecycle-callback-class>test.TestServlet</lifecycle-callback-class> + <lifecycle-callback-method>postConstruct</lifecycle-callback-method> + </post-construct> + <pre-destroy> + <lifecycle-callback-class>test.TestServlet</lifecycle-callback-class> + <lifecycle-callback-method>preDestroy</lifecycle-callback-method> + </pre-destroy> +</web-app> \ No newline at end of file Propchange: tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml ------------------------------------------------------------------------------ svn:eol-style = native Added: tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml?rev=1431293&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml (added) +++ tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml Thu Jan 10 11:39:46 2013 @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<web-app xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee + http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + version="3.0" + metadata-complete="true"> + <post-construct> + <lifecycle-callback-class>test.TestServlet</lifecycle-callback-class> + <lifecycle-callback-method>postConstruct1</lifecycle-callback-method> + </post-construct> + <post-construct> + <lifecycle-callback-class>test.TestServlet</lifecycle-callback-class> + <lifecycle-callback-method>postConstruct2</lifecycle-callback-method> + </post-construct> +</web-app> \ No newline at end of file Propchange: tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml?rev=1431293&r1=1431292&r2=1431293&view=diff ============================================================================== --- tomcat/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml (original) +++ tomcat/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml Thu Jan 10 11:39:46 2013 @@ -49,4 +49,12 @@ <jsp-file>/bug5nnnn/bug51396.jsp</jsp-file> </servlet> + <post-construct> + <lifecycle-callback-class>org.apache.catalina.startup.TesterServletWithLifeCycleMethods</lifecycle-callback-class> + <lifecycle-callback-method>postConstruct1</lifecycle-callback-method> + </post-construct> + <pre-destroy> + <lifecycle-callback-class>org.apache.catalina.startup.TesterServletWithLifeCycleMethods</lifecycle-callback-class> + <lifecycle-callback-method>preDestroy1</lifecycle-callback-method> + </pre-destroy> </web-app> \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org