--- 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 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