Author: markt Date: Thu Jan 10 11:47:49 2013 New Revision: 1431300 URL: http://svn.apache.org/viewvc?rev=1431300&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/tc7.0.x/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java - copied unchanged from r1431293, tomcat/trunk/test/org/apache/catalina/startup/TesterServletWithLifeCycleMethods.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml - copied unchanged from r1431293, tomcat/trunk/test/org/apache/catalina/startup/web-1lifecyclecallback.xml tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml - copied unchanged from r1431293, tomcat/trunk/test/org/apache/catalina/startup/web-2lifecyclecallback.xml Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/catalina/Context.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardContext.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/deploy/WebXml.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/FailedContext.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/WebRuleSet.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/util/Introspection.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestStandardContext.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/deploy/TestWebXml.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestContextConfig.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java tomcat/tc7.0.x/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1431293 Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/Context.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/Context.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/Context.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/Context.java Thu Jan 10 11:47:49 2013 @@ -21,6 +21,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; @@ -1424,5 +1425,101 @@ public interface Context extends Contain * part of a redirect response. */ public boolean getSendRedirectBody(); -} + /** + * 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/tc7.0.x/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/DefaultInstanceManager.java Thu Jan 10 11:47:49 2013 @@ -19,10 +19,10 @@ package org.apache.catalina.core; import java.beans.Introspector; 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; @@ -80,6 +80,8 @@ public class DefaultInstanceManager impl private final Properties restrictedServlets = new Properties(); private final Map<Class<?>, AnnotationCacheEntry[]> annotationCache = new WeakHashMap<Class<?>, AnnotationCacheEntry[]>(); + 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(); @@ -126,6 +128,8 @@ public class DefaultInstanceManager impl } this.context = context; this.injectionMap = injectionMap; + this.postConstructMethods = catalinaContext.findPostConstructMethods(); + this.preDestroyMethods = catalinaContext.findPreDestroyMethods(); } @Override @@ -332,7 +336,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 @@ -389,41 +395,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 @@ -711,6 +706,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/tc7.0.x/trunk/java/org/apache/catalina/core/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/LocalStrings.properties?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/LocalStrings.properties Thu Jan 10 11:47:49 2013 @@ -145,6 +145,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.reloadingFailed=Reloading this Context failed due to previous errors standardContext.reloadingStarted=Reloading Context with name [{0}] has started Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardContext.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardContext.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardContext.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardContext.java Thu Jan 10 11:47:49 2013 @@ -864,6 +864,12 @@ public class StandardContext extends Con private boolean jndiExceptionOnFailedWrite = true; + private Map<String, String> postConstructMethods = + new HashMap<String, String>(); + private Map<String, String> preDestroyMethods = + new HashMap<String, String>(); + + // ----------------------------------------------------- Context Properties @Override @@ -5994,6 +6000,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/tc7.0.x/trunk/java/org/apache/catalina/deploy/WebXml.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/deploy/WebXml.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/deploy/WebXml.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/deploy/WebXml.java Thu Jan 10 11:47:49 2013 @@ -568,6 +568,29 @@ public class WebXml { return localeEncodingMappings; } + // post-construct elements + private Map<String, String> postConstructMethods = + new HashMap<String, String>(); + 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<String, String>(); + 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 @@ -1075,6 +1098,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()); @@ -1374,6 +1423,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()); + } } /** @@ -1870,6 +1927,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; } @@ -2094,6 +2173,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/tc7.0.x/trunk/java/org/apache/catalina/startup/FailedContext.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/FailedContext.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/FailedContext.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/FailedContext.java Thu Jan 10 11:47:49 2013 @@ -20,6 +20,7 @@ import java.beans.PropertyChangeListener import java.io.IOException; import java.net.URL; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.naming.directory.DirContext; @@ -656,4 +657,28 @@ public class FailedContext extends Lifec @Override public Object getMappingObject() { return null; } + + @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/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties Thu Jan 10 11:47:49 2013 @@ -136,6 +136,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/tc7.0.x/trunk/java/org/apache/catalina/startup/WebRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/WebRuleSet.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/WebRuleSet.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/WebRuleSet.java Thu Jan 10 11:47:49 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/tc7.0.x/trunk/java/org/apache/catalina/util/Introspection.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/util/Introspection.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/util/Introspection.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/util/Introspection.java Thu Jan 10 11:47:49 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/tc7.0.x/trunk/test/org/apache/catalina/core/TestStandardContext.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestStandardContext.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestStandardContext.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestStandardContext.java Thu Jan 10 11:47:49 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/tc7.0.x/trunk/test/org/apache/catalina/deploy/TestWebXml.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/deploy/TestWebXml.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/deploy/TestWebXml.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/deploy/TestWebXml.java Thu Jan 10 11:47:49 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<WebXml>(); + 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<WebXml>(); + 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<WebXml>(); + 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/tc7.0.x/trunk/test/org/apache/catalina/startup/TestContextConfig.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestContextConfig.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestContextConfig.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestContextConfig.java Thu Jan 10 11:47:49 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/tc7.0.x/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/startup/TestWebRuleSet.java Thu Jan 10 11:47:49 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 { Modified: tomcat/tc7.0.x/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml (original) +++ tomcat/tc7.0.x/trunk/test/webapp-3.0-fragments/WEB-INF/web.xml Thu Jan 10 11:47:49 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 Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1431300&r1=1431299&r2=1431300&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Thu Jan 10 11:47:49 2013 @@ -114,6 +114,10 @@ RemoteIpFilter. Patch by Violeta Georgieva. (markt) </fix> <fix> + <bug>54379</bug>: Implement support for post-construct and pre-destroy + elements in web.xml. Patch by Violeta Georgieva. (markt) + </fix> + <fix> <bug>54380</bug>: Do not try to register servlets or contexts into the mapper too early (which just caused a warning to be logged). (kkolinko) </fix> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org