Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=51278
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 2 Jun 2011 15:54:26 +0000 (15:54 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 2 Jun 2011 15:54:26 +0000 (15:54 +0000)
Allow ServletContainerInitializers to override settings in the global default web.xml and the host web.xml.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1130618 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/Wrapper.java
java/org/apache/catalina/core/ApplicationContext.java
java/org/apache/catalina/core/ApplicationServletRegistration.java
java/org/apache/catalina/core/StandardWrapper.java
java/org/apache/catalina/deploy/ServletDef.java
java/org/apache/catalina/deploy/WebXml.java
java/org/apache/catalina/startup/ContextConfig.java
test/org/apache/catalina/startup/TestContextConfig.java [new file with mode: 0644]
test/webapp-3.0/index.html [new file with mode: 0644]
webapps/docs/changelog.xml

index cc67b4c..2580def 100644 (file)
@@ -386,4 +386,14 @@ public interface Wrapper extends Container {
      * Servlet associated with this wrapper.
      */
     public void servletSecurityAnnotationScan() throws ServletException;
+    
+    /**
+     * Is the Servlet overridable by a ServletContainerInitializer?
+     */
+    public boolean isOverridable();
+
+    /**
+     * Sets the overridable attribute for this Servlet.
+     */
+    public void setOverridable(boolean overridable);
 }
index 22925c2..bfd68bc 100644 (file)
@@ -1102,7 +1102,7 @@ public class ApplicationContext
 
         Wrapper wrapper = (Wrapper) context.findChild(servletName);
         
-        // Assume a 'complete' FilterRegistration is one that has a class and
+        // Assume a 'complete' ServletRegistration is one that has a class and
         // a name
         if (wrapper == null) {
             wrapper = context.createWrapper();
@@ -1111,7 +1111,11 @@ public class ApplicationContext
         } else {
             if (wrapper.getName() != null &&
                     wrapper.getServletClass() != null) {
-                return null;
+                if (wrapper.isOverridable()) {
+                    wrapper.setOverridable(false);
+                } else {
+                    return null;
+                }
             }
         }
 
index c4bb9fd..707a88d 100644 (file)
@@ -171,8 +171,16 @@ public class ApplicationServletRegistration
         Set<String> conflicts = new HashSet<String>();
         
         for (String urlPattern : urlPatterns) {
-            if (context.findServletMapping(urlPattern) != null) {
-                conflicts.add(urlPattern);
+            String wrapperName = context.findServletMapping(urlPattern);
+            if (wrapperName != null) {
+                Wrapper wrapper = (Wrapper) context.findChild(wrapperName);
+                if (wrapper.isOverridable()) {
+                    // Some Wrappers (from global and host web.xml) may be
+                    // overridden rather than generating a conflict
+                    context.removeServletMapping(urlPattern);
+                } else {
+                    conflicts.add(urlPattern);
+                }
             }
         }
 
index bbd6f31..7b06fbc 100644 (file)
@@ -276,6 +276,8 @@ public class StandardWrapper extends ContainerBase
 
     protected volatile boolean servletSecurityAnnotationScanRequired = false;
 
+    private boolean overridable = false;
+    
     /**
      * Static class array used when the SecurityManager is turned on and 
      * <code>Servlet.init</code> is invoked.
@@ -294,6 +296,15 @@ public class StandardWrapper extends ContainerBase
 
     // ------------------------------------------------------------- Properties
 
+    @Override
+    public boolean isOverridable() {
+        return overridable;
+    }
+
+    @Override
+    public void setOverridable(boolean overridable) {
+        this.overridable = overridable;
+    }
 
     /**
      * Return the available date/time for this servlet, in milliseconds since
index 7b410b7..44261a5 100644 (file)
@@ -264,4 +264,19 @@ public class ServletDef implements Serializable {
     public void setEnabled(String enabled) {
         this.enabled = Boolean.valueOf(enabled);
     }
+
+    
+    /**
+     * Can this ServletDef be overridden by an SCI?
+     */
+    private boolean overridable = false;
+    
+    public boolean isOverridable() {
+        return overridable;
+    }
+
+    public void setOverridable(boolean overridable) {
+        this.overridable = overridable;
+    }
+
 }
index 230e9fc..99ec991 100644 (file)
@@ -61,7 +61,18 @@ public class WebXml {
 
     private static final org.apache.juli.logging.Log log=
         org.apache.juli.logging.LogFactory.getLog(WebXml.class);
-    
+
+    // Global defaults are overridable but Servlets and Servlet mappings need to
+    // be unique. Duplicates normally trigger an error. This flag indicates if
+    // newly added Servlet elements are marked as overridable.
+    private boolean overridable = false;
+    public boolean isOverridable() {
+        return overridable;
+    }
+    public void setOverridable(boolean overridable) {
+        this.overridable = overridable;
+    }
+
     // web.xml only elements
     // Absolute Ordering
     private Set<String> absoluteOrdering = null;
@@ -305,6 +316,9 @@ public class WebXml {
     private Map<String,ServletDef> servlets = new HashMap<String,ServletDef>();
     public void addServlet(ServletDef servletDef) {
         servlets.put(servletDef.getServletName(), servletDef);
+        if (overridable) {
+            servletDef.setOverridable(overridable);
+        }
     }
     public Map<String,ServletDef> getServlets() { return servlets; }
     
@@ -1268,6 +1282,7 @@ public class WebXml {
                 wrapper.setAsyncSupported(
                         servlet.getAsyncSupported().booleanValue());
             }
+            wrapper.setOverridable(servlet.isOverridable());
             context.addChild(wrapper);
         }
         for (Entry<String, String> entry : servletMappings.entrySet()) {
index 4e9e556..dba237b 100644 (file)
@@ -1186,6 +1186,15 @@ public class ContextConfig
      */
     protected void webConfig() {
         WebXml webXml = createWebXml();
+        /* Anything and everything can override the global and host defaults.
+         * This is implemented in two parts
+         * - Handle as a web fragment that gets added after everything else so
+         *   everything else takes priority
+         * - Mark Servlets as overridable so SCI configuration can replace
+         *   configuration from the defaults
+         */ 
+        WebXml webXmlDefaultFragment = createWebXml();
+        webXmlDefaultFragment.setOverridable(true);
 
         // Parse global web.xml if present
         InputSource globalWebXml = getGlobalWebXmlSource();
@@ -1193,17 +1202,19 @@ public class ContextConfig
             // This is unusual enough to log
             log.info(sm.getString("contextConfig.defaultMissing"));
         } else {
-            parseWebXml(globalWebXml, webXml, false);
+            parseWebXml(globalWebXml, webXmlDefaultFragment, false);
         }
 
         // Parse host level web.xml if present
         // Additive apart from welcome pages
         webXml.setReplaceWelcomeFiles(true);
         InputSource hostWebXml = getHostWebXmlSource();
-        parseWebXml(hostWebXml, webXml, false);
+        parseWebXml(hostWebXml, webXmlDefaultFragment, false);
+        
+        Set<WebXml> defaults = new HashSet<WebXml>();
+        defaults.add(webXmlDefaultFragment);
         
         // Parse context level web.xml
-        webXml.setReplaceWelcomeFiles(true);
         InputSource contextWebXml = getContextWebXmlSource();
         parseWebXml(contextWebXml, webXml, false);
         
@@ -1260,16 +1271,19 @@ public class ContextConfig
                     ok = webXml.merge(orderedFragments);
                 }
     
-                // Step 6.5 Convert explicitly mentioned jsps to servlets
+                // Step 7. Convert explicitly mentioned jsps to servlets
                 if (!false) {
                     convertJsps(webXml);
                 }
-    
-                // Step 7. Apply merged web.xml to Context
+
+                // Step 8. Apply global defaults
+                webXml.merge(defaults);
+                
+                // Step 9. Apply merged web.xml to Context
                 if (ok) {
                     webXml.configureContext(context);
     
-                    // Step 7a. Make the merged web.xml available to other
+                    // Step 9a. Make the merged web.xml available to other
                     // components, specifically Jasper, to save those components
                     // from having to re-generate it.
                     // TODO Use a ServletContainerInitializer for Jasper
@@ -1282,11 +1296,12 @@ public class ContextConfig
                     }
                 }
             } else {
+                webXml.merge(defaults);
                 webXml.configureContext(context);
             }
             
             // Always need to look for static resources
-            // Step 8. Look for static resources packaged in JARs
+            // Step 10. Look for static resources packaged in JARs
             if (ok) {
                 // Spec does not define an order.
                 // Use ordered JARs followed by remaining JARs
@@ -1309,7 +1324,7 @@ public class ContextConfig
             // Only look for ServletContainerInitializer if metadata is not
             // complete
             if (!webXml.isMetadataComplete()) {
-                // Step 9. Apply the ServletContainerInitializer config to the
+                // Step 11. Apply the ServletContainerInitializer config to the
                 // context
                 if (ok) {
                     for (Map.Entry<ServletContainerInitializer,
@@ -1328,6 +1343,7 @@ public class ContextConfig
         } else {
             // Apply unmerged web.xml to Context
             convertJsps(webXml);
+            webXml.merge(defaults);
             webXml.configureContext(context);
         }
     }
diff --git a/test/org/apache/catalina/startup/TestContextConfig.java b/test/org/apache/catalina/startup/TestContextConfig.java
new file mode 100644 (file)
index 0000000..3c78258
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.util.Set;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TestContextConfig extends TomcatBaseTest {
+
+    public void testOverrideWithSCIDefaultName() throws Exception {
+        doTestOverrideDefaultServletWithSCI("default");
+    }
+
+    public void testOverrideWithSCIDefaultMapping() throws Exception {
+        doTestOverrideDefaultServletWithSCI("anything");
+    }
+
+    private void doTestOverrideDefaultServletWithSCI(String servletName)
+            throws Exception{
+
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp-3.0");
+        StandardContext ctxt = (StandardContext) tomcat.addContext(null,
+                "/test", appDir.getAbsolutePath());
+        ctxt.setDefaultWebXml(new File("conf/web.xml").getAbsolutePath());
+        ctxt.addLifecycleListener(new ContextConfig());
+        
+        ctxt.addServletContainerInitializer(
+                new CustomDefaultServletSCI(servletName), null);
+
+        tomcat.start();
+
+        ByteChunk res = new ByteChunk();
+        
+        int rc =getUrl("http://localhost:" + getPort() + "/test", res, null);
+
+        // Check return code
+        assertEquals(HttpServletResponse.SC_OK, rc);
+        
+        // Check context
+        assertEquals("OK - Custom default Servlet", res.toString());
+    }
+
+    private static class CustomDefaultServletSCI
+            implements ServletContainerInitializer {
+
+        private String servletName;
+        
+        public CustomDefaultServletSCI(String servletName) {
+            this.servletName = servletName;
+        }
+
+        @Override
+        public void onStartup(Set<Class<?>> c, ServletContext ctx)
+                throws ServletException {
+            Servlet s = new CustomDefaultServlet();
+            ServletRegistration.Dynamic r = ctx.addServlet(servletName, s);
+            r.addMapping("/");
+        }
+        
+    }
+
+    private static class CustomDefaultServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.getWriter().print("OK - Custom default Servlet");
+        }
+    }
+}
diff --git a/test/webapp-3.0/index.html b/test/webapp-3.0/index.html
new file mode 100644 (file)
index 0000000..2bf0cef
--- /dev/null
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title>Index page</title>
+  </head>
+  <body>
+   <p>This is the index page served by the default Servlet.</p>
+  </body>
+</html>
\ No newline at end of file
index 226f7d9..5fbc07d 100644 (file)
         with an incomplete FORM authentication configuration. (markt)
       </fix>
       <fix>
+        <bug>51278</bug>: Allow ServletContainerInitializers to override
+        settings in the global default web.xml and the host web.xml. (markt)
+      </fix>
+      <fix>
         <bug>51310</bug>: When stopping the Server object on shutdown call
         destroy() after calling stop(). (markt)
       </fix>