--- java/org/apache/catalina/Context.java (revision 1431196)
+++ java/org/apache/catalina/Context.java (working copy)
@@ -21,6 +21,7 @@
import java.net.URL;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
@@ -1424,5 +1425,102 @@
* 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 class Fully qualified class name
+ * @param method
+ * Post construct method name
+ * @throws IllegalArgumentException
+ * if the fully qualified class name or method name are
+ * NULL
; 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 class Fully qualified class name
+ * @param method
+ * Post construct method name
+ * @throws IllegalArgumentException
+ * if the fully qualified class name or method name are
+ * NULL
; 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 NULL
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 NULL
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 NULL
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 NULL
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 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 findPreDestroyMethods();
}
--- java/org/apache/catalina/core/LocalStrings.properties (revision 1431196)
+++ java/org/apache/catalina/core/LocalStrings.properties (working copy)
@@ -145,6 +145,10 @@
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
--- java/org/apache/catalina/core/DefaultInstanceManager.java (revision 1431196)
+++ java/org/apache/catalina/core/DefaultInstanceManager.java (working copy)
@@ -19,10 +19,10 @@
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 @@
private final Properties restrictedServlets = new Properties();
private final Map, AnnotationCacheEntry[]> annotationCache =
new WeakHashMap, AnnotationCacheEntry[]>();
+ private final Map postConstructMethods;
+ private final Map preDestroyMethods;
public DefaultInstanceManager(Context context, Map> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) {
classLoader = catalinaContext.getLoader().getClassLoader();
@@ -126,6 +128,8 @@
}
this.context = context;
this.injectionMap = injectionMap;
+ this.postConstructMethods = catalinaContext.findPostConstructMethods();
+ this.preDestroyMethods = catalinaContext.findPreDestroyMethods();
}
@Override
@@ -332,7 +336,9 @@
// Initialize methods annotations
Method[] methods = Introspection.getDeclaredMethods(clazz);
Method postConstruct = null;
+ String postConstructFromXml = this.postConstructMethods.get(clazz.getName());
Method preDestroy = null;
+ String preDestroyFromXml = this.preDestroyMethods.get(clazz.getName());
for (Method method : methods) {
if (context != null) {
// Resource injection only if JNDI is enabled
@@ -389,41 +395,30 @@
}
}
- 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
@@ -745,4 +740,40 @@
private static enum AnnotationCacheEntryType {
FIELD, SETTER, POST_CONSTRUCT, PRE_DESTROY
}
+
+ private Method findPostConstruct(Method currentPostConstruct,
+ String postConstructFromXml, Method method) {
+ return findLifecycleCallback(currentPostConstruct,
+ postConstructFromXml, method, PostConstruct.class);
+ }
+
+ private Method findPreDestroy(Method currentPreDestroy,
+ String preDestroyFromXml, Method method) {
+ return findLifecycleCallback(currentPreDestroy,
+ preDestroyFromXml, method, PreDestroy.class);
+ }
+
+ private 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;
+ }
}
--- java/org/apache/catalina/core/StandardContext.java (revision 1431196)
+++ java/org/apache/catalina/core/StandardContext.java (working copy)
@@ -6520,4 +6520,78 @@
public boolean isStatisticsProvider() {
return false;
}
+
+ private Map postConstructMethods = new HashMap();
+
+ @Override
+ public void addPostConstructMethod(String clazz, String method) {
+ if (clazz == null || method == null)
+ throw new IllegalArgumentException(
+ sm.getString("standardContext.postconstruct.required"));
+ if (this.postConstructMethods.get(clazz) != null)
+ throw new IllegalArgumentException(sm.getString(
+ "standardContext.postconstruct.duplicate", clazz));
+
+ synchronized (this.postConstructMethods) {
+ this.postConstructMethods.put(clazz, method);
+ }
+ fireContainerEvent("addPostConstructMethod", clazz);
+ }
+
+ @Override
+ public void removePostConstructMethod(String clazz) {
+ synchronized (this.postConstructMethods) {
+ this.postConstructMethods.remove(clazz);
+ }
+ fireContainerEvent("removePostConstructMethod", clazz);
+ }
+
+ private Map preDestroyMethods = new HashMap();
+
+ @Override
+ public void addPreDestroyMethod(String clazz, String method) {
+ if (clazz == null || method == null)
+ throw new IllegalArgumentException(
+ sm.getString("standardContext.predestroy.required"));
+ if (this.preDestroyMethods.get(clazz) != null)
+ throw new IllegalArgumentException(sm.getString(
+ "standardContext.predestroy.duplicate", clazz));
+
+ synchronized (this.preDestroyMethods) {
+ this.preDestroyMethods.put(clazz, method);
+ }
+ fireContainerEvent("addPreDestroyMethod", clazz);
+ }
+
+ @Override
+ public void removePreDestroyMethod(String clazz) {
+ synchronized (this.preDestroyMethods) {
+ this.preDestroyMethods.remove(clazz);
+ }
+ fireContainerEvent("removePreDestroyMethod", clazz);
+ }
+
+ @Override
+ public String findPostConstructMethod(String clazz) {
+ synchronized (this.postConstructMethods) {
+ return (this.postConstructMethods.get(clazz));
+ }
+ }
+
+ @Override
+ public String findPreDestroyMethod(String clazz) {
+ synchronized (this.preDestroyMethods) {
+ return (this.preDestroyMethods.get(clazz));
+ }
+ }
+
+ @Override
+ public Map findPostConstructMethods() {
+ return this.postConstructMethods;
+ }
+
+ @Override
+ public Map findPreDestroyMethods() {
+ return this.preDestroyMethods;
+ }
}
--- java/org/apache/catalina/deploy/WebXml.java (revision 1431196)
+++ java/org/apache/catalina/deploy/WebXml.java (working copy)
@@ -568,6 +568,27 @@
return localeEncodingMappings;
}
+ // post-construct elements
+ private Map postConstructMethods = new HashMap();
+ public void addPostConstructMethods(String clazz, String method) {
+ if (!this.postConstructMethods.containsKey(clazz)) {
+ this.postConstructMethods.put(clazz, method);
+ }
+ }
+ public Map getPostConstructMethods() {
+ return this.postConstructMethods;
+ }
+
+ // pre-destroy elements
+ private Map preDestroyMethods = new HashMap();
+ public void addPreDestroyMethods(String clazz, String method) {
+ if (!this.preDestroyMethods.containsKey(clazz)) {
+ this.preDestroyMethods.put(clazz, method);
+ }
+ }
+ public Map getPreDestroyMethods() {
+ return this.preDestroyMethods;
+ }
// Attributes not defined in web.xml or web-fragment.xml
@@ -1075,6 +1096,32 @@
}
sb.append('\n');
+ if (!this.postConstructMethods.isEmpty()) {
+ for (Entry entry : this.postConstructMethods
+ .entrySet()) {
+ sb.append(" \n");
+ appendElement(sb, INDENT4, "lifecycle-callback-class",
+ entry.getKey());
+ appendElement(sb, INDENT4, "lifecycle-callback-method",
+ entry.getValue());
+ sb.append(" \n");
+ }
+ sb.append('\n');
+ }
+
+ if (!this.preDestroyMethods.isEmpty()) {
+ for (Entry entry : this.preDestroyMethods
+ .entrySet()) {
+ sb.append(" \n");
+ appendElement(sb, INDENT4, "lifecycle-callback-class",
+ entry.getKey());
+ appendElement(sb, INDENT4, "lifecycle-callback-method",
+ entry.getValue());
+ sb.append(" \n");
+ }
+ sb.append('\n');
+ }
+
for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
sb.append(" \n");
appendElement(sb, INDENT4, "description", mdr.getDescription());
@@ -1374,6 +1421,14 @@
}
}
}
+
+ for (Entry entry : this.postConstructMethods.entrySet()) {
+ context.addPostConstructMethod(entry.getKey(), entry.getValue());
+ }
+
+ for (Entry entry : this.preDestroyMethods.entrySet()) {
+ context.addPreDestroyMethod(entry.getKey(), entry.getValue());
+ }
}
/**
@@ -1870,6 +1925,28 @@
}
}
+ if (this.postConstructMethods.isEmpty()) {
+ for (WebXml fragment : fragments) {
+ if (!mergeLifecycleCallback(fragment.getPostConstructMethods(),
+ temp.getPostConstructMethods(), fragment,
+ "Post Construct Methods")) {
+ return false;
+ }
+ }
+ this.postConstructMethods.putAll(temp.getPostConstructMethods());
+ }
+
+ if (this.preDestroyMethods.isEmpty()) {
+ for (WebXml fragment : fragments) {
+ if (!mergeLifecycleCallback(fragment.getPreDestroyMethods(),
+ temp.getPreDestroyMethods(), fragment,
+ "Pre Destroy Methods")) {
+ return false;
+ }
+ }
+ this.preDestroyMethods.putAll(temp.getPreDestroyMethods());
+ }
+
return true;
}
@@ -2094,6 +2171,26 @@
}
+ private static boolean mergeLifecycleCallback(
+ Map fragmentMap, Map tempMap,
+ WebXml fragment, String mapName) {
+ for (Entry 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
--- java/org/apache/catalina/startup/LocalStrings.properties (revision 1431196)
+++ java/org/apache/catalina/startup/LocalStrings.properties (working copy)
@@ -136,6 +136,8 @@
webRuleSet.absoluteOrdering= element not valid in web-fragment.xml and will be ignored
webRuleSet.absoluteOrderingCount= element is limited to 1 occurrence
webRuleSet.nameCount= 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= element not valid in web.xml and will be ignored
webRuleSet.relativeOrderingCount= element is limited to 1 occurrence
xmlErrorHandler.error=Non-fatal error [{0}] reported processing [{1}].
--- java/org/apache/catalina/startup/WebRuleSet.java (revision 1431196)
+++ java/org/apache/catalina/startup/WebRuleSet.java (working copy)
@@ -473,6 +473,15 @@
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,38 @@
ResourceBase resourceBase = (ResourceBase) digester.peek();
resourceBase.setProperty("mappedName", text.trim());
}
-}
+}
+
+/**
+ * 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);
+ }
+}
--- java/org/apache/catalina/startup/FailedContext.java (revision 1431196)
+++ java/org/apache/catalina/startup/FailedContext.java (working copy)
@@ -20,6 +20,7 @@
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 @@
@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 findPostConstructMethods() { return null; }
+
+ @Override
+ public Map findPreDestroyMethods() { return null; }
}
--- java/org/apache/catalina/util/Introspection.java (revision 1431196)
+++ java/org/apache/catalina/util/Introspection.java (working copy)
@@ -19,6 +19,7 @@
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 @@
return false;
}
+ /**
+ * Determines if a method is a valid lifecycle callback method.
+ *
+ * @param method
+ * The method to test
+ *
+ * @return true
if the method is a valid lifecycle callback
+ * method, else false
+ */
+ 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
--- test/org/apache/catalina/core/TestStandardContext.java (revision 1431196)
+++ test/org/apache/catalina/core/TestStandardContext.java (working copy)
@@ -754,4 +754,38 @@
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");
+ }
}
--- test/org/apache/catalina/deploy/TestWebXml.java (revision 1431196)
+++ test/org/apache/catalina/deploy/TestWebXml.java (working copy)
@@ -18,6 +18,11 @@
package org.apache.catalina.deploy;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import org.junit.Test;
@@ -133,4 +138,87 @@
assertEquals(0, webxml.getMinorVersion());
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 fragments = new HashSet();
+ fragments.add(fragment);
+
+ webxml.merge(fragments);
+
+ Map postConstructMethods = webxml.getPostConstructMethods();
+ Map preDestroyMethods = webxml.getPreDestroyMethods();
+ assertEquals(1, postConstructMethods.size());
+ assertEquals(1, preDestroyMethods.size());
+
+ assertEquals("a", postConstructMethods.get("a"));
+ 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 fragments = new HashSet();
+ fragments.add(fragment1);
+ fragments.add(fragment2);
+
+ webxml.merge(fragments);
+
+ Map postConstructMethods = webxml.getPostConstructMethods();
+ Map preDestroyMethods = webxml.getPreDestroyMethods();
+ assertEquals(2, postConstructMethods.size());
+ assertEquals(2, preDestroyMethods.size());
+
+ assertEquals("a", postConstructMethods.get("a"));
+ assertEquals("c", postConstructMethods.get("c"));
+ assertEquals("b", preDestroyMethods.get("b"));
+ 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 fragments = new HashSet();
+ fragments.add(fragment1);
+ fragments.add(fragment2);
+
+ assertFalse(webxml.merge(fragments));
+
+ assertEquals(0, webxml.getPostConstructMethods().size());
+
+ WebXml fragment3 = new WebXml();
+ fragment3.addPreDestroyMethods("b", "b");
+
+ fragments.remove(fragment2);
+ fragments.add(fragment3);
+
+ assertFalse(webxml.merge(fragments));
+
+ assertEquals(0, webxml.getPreDestroyMethods().size());
+ }
}
--- test/org/apache/catalina/startup/TestWebRuleSet.java (revision 1431196)
+++ test/org/apache/catalina/startup/TestWebRuleSet.java (working copy)
@@ -115,6 +115,13 @@
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 {
--- test/org/apache/catalina/startup/TestContextConfig.java (revision 1431196)
+++ test/org/apache/catalina/startup/TestContextConfig.java (working copy)
@@ -32,6 +32,7 @@
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 @@
null, HttpServletResponse.SC_NOT_FOUND);
}
+ @Test
+ public void testBug54379() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0-fragments");
+ String oldValue = System.setProperty("catalina.useNaming", "true");
+ Context context = tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
+
+ Tomcat.addServlet(context, "TestServlet",
+ "org.apache.catalina.startup.ServletWithLifeCycleMethods");
+ context.addServletMapping("/testServlet", "TestServlet");
+
+ tomcat.start();
+
+ assertPageContains("/test/testServlet", "postConstruct1()");
+
+ System.setProperty("catalina.useNaming", oldValue);
+ }
+
private static class CustomDefaultServletSCI
implements ServletContainerInitializer {
--- test/webapp-3.0-fragments/WEB-INF/web.xml (revision 1431196)
+++ test/webapp-3.0-fragments/WEB-INF/web.xml (working copy)
@@ -49,4 +49,12 @@
/bug5nnnn/bug51396.jsp
+
+ org.apache.catalina.startup.ServletWithLifeCycleMethods
+ postConstruct1
+
+
+ org.apache.catalina.startup.ServletWithLifeCycleMethods
+ preDestroy1
+
--- test/org/apache/catalina/startup/ServletWithLifeCycleMethods.java (revision 0)
+++ test/org/apache/catalina/startup/ServletWithLifeCycleMethods.java (working copy)
@@ -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 ServletWithLifeCycleMethods 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(this.result);
+ }
+
+ @PostConstruct
+ protected void postConstruct() {
+ this.result = "postConstruct()";
+ }
+
+ @PreDestroy
+ protected void preDestroy() {
+ this.result = "preDestroy()";
+ }
+
+ protected void postConstruct1() {
+ this.result = "postConstruct1()";
+ }
+
+ protected void preDestroy1() {
+ this.result = "preDestroy1()";
+ }
+}
+native
--- test/org/apache/catalina/startup/web-1lifecyclecallback.xml (revision 0)
+++ test/org/apache/catalina/startup/web-1lifecyclecallback.xml (working copy)
@@ -0,0 +1,32 @@
+
+
+
+
+ test.TestServlet
+ postConstruct
+
+
+ test.TestServlet
+ preDestroy
+
+
+native
--- test/org/apache/catalina/startup/web-2lifecyclecallback.xml (revision 0)
+++ test/org/apache/catalina/startup/web-2lifecyclecallback.xml (working copy)
@@ -0,0 +1,32 @@
+
+
+
+
+ test.TestServlet
+ postConstruct1
+
+
+ test.TestServlet
+ postConstruct2
+
+
+native