Re-factoring. WebXml is a better fit in o.a.c.deploy
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Fri, 15 Jan 2010 18:04:22 +0000 (18:04 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Fri, 15 Jan 2010 18:04:22 +0000 (18:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@899737 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/deploy/Constants.java [new file with mode: 0644]
java/org/apache/catalina/deploy/LocalStrings.properties [new file with mode: 0644]
java/org/apache/catalina/deploy/WebXml.java [new file with mode: 0644]
java/org/apache/catalina/startup/ContextConfig.java
java/org/apache/catalina/startup/LocalStrings.properties
java/org/apache/catalina/startup/WebRuleSet.java
java/org/apache/catalina/startup/WebXml.java [deleted file]
test/org/apache/TestAll.java
test/org/apache/catalina/deploy/TestWebXml.java [new file with mode: 0644]
test/org/apache/catalina/startup/TestWebXml.java [deleted file]

diff --git a/java/org/apache/catalina/deploy/Constants.java b/java/org/apache/catalina/deploy/Constants.java
new file mode 100644 (file)
index 0000000..9ac67ae
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.deploy;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.deploy";
+
+}
diff --git a/java/org/apache/catalina/deploy/LocalStrings.properties b/java/org/apache/catalina/deploy/LocalStrings.properties
new file mode 100644 (file)
index 0000000..382417f
--- /dev/null
@@ -0,0 +1,41 @@
+# 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.
+
+webXml.duplicateEnvEntry=Duplicate env-entry name [{0}]
+webXml.duplicateFilter=Duplicate filter name [{0}]
+webXml.duplicateMessageDestination=Duplicate message-destination name [{0}]
+webXml.duplicateMessageDestinationRef=Duplicate message-destination-ref name [{0}]
+webXml.duplicateResourceEnvRef=Duplicate resource-env-ref name [{0}]
+webXml.duplicateResourceRef=Duplicate resource-ref name [{0}]
+webXml.duplicateTaglibUri=Duplicate tag library URI [{0}]
+webXml.reservedName=A web.xml file was detected using a reserved name [{0}]. The name element will be ignored for this fragment.
+webXml.mergeConflictDisplayName=The display name was defined in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictErrorPage=The Error Page for [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictFilter=The Filter [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictLoginConfig=A LoginConfig was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictOrder=Fragment relative ordering contains circular references. Thsi can be resolved by using absolute ordering in web.xml.
+webXml.mergeConflictResource=The Resource [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictServlet=The Servlet [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictSessionCookieName=The session cookie name was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieDomain=The session cookie domain was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookiePath=The session cookie path was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieComment=The session cookie comment was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieHttpOnly=The session cookie http-only flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieSecure=The session cookie secure flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieMaxAge=The session cookie max-age was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionTimeout=The session timeout was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionTrackingMode=The session tracking modes were defined inconsistently in multiple fragments including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictString=The [{0}] with name [{1}] was defined inconsistently in multiple fragments including fragment with name [{2}] located at [{3}]
+webXml.multipleOther=Multiple others entries in ordering
\ No newline at end of file
diff --git a/java/org/apache/catalina/deploy/WebXml.java b/java/org/apache/catalina/deploy/WebXml.java
new file mode 100644 (file)
index 0000000..121b134
--- /dev/null
@@ -0,0 +1,2030 @@
+/*
+ * 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.deploy;
+
+import java.net.URL;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Representation of common elements of web.xml and web-fragment.xml. Provides
+ * a repository for parsed data before the elements are merged.
+ * Validation is spread between multiple classes:
+ * The digester checks for structural correctness (eg single login-config)
+ * This class checks for invalid duplicates (eg filter/servlet names)
+ * StandardContext will check validity of values (eg URL formats etc)
+ */
+public class WebXml {
+    
+    protected static final String ORDER_OTHERS =
+        "org.apache.catalina.order.others";
+    
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(WebXml.class);
+    
+    // web.xml only elements
+    // Absolute Ordering
+    private Set<String> absoluteOrdering = null;
+    public void addAbsoluteOrdering(String fragmentName) {
+        if (absoluteOrdering == null) {
+            absoluteOrdering = new LinkedHashSet<String>();
+        }
+        absoluteOrdering.add(fragmentName);
+    }
+    public void addAbsoluteOrderingOthers() {
+        if (absoluteOrdering == null) {
+            absoluteOrdering = new LinkedHashSet<String>();
+        }
+        absoluteOrdering.add(ORDER_OTHERS);
+    }
+    public Set<String> getAbsoluteOrdering() {
+        return absoluteOrdering;
+    }
+
+    // web-fragment.xml only elements
+    // Relative ordering
+    private Set<String> after = new LinkedHashSet<String>();
+    public void addAfterOrder(String fragmentName) {
+        after.add(fragmentName);
+    }
+    public void addAfterOrderOthers() {
+        if (before.contains(ORDER_OTHERS)) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.multipleOther"));
+        }
+        after.add(ORDER_OTHERS);
+    }
+    public Set<String> getAfterOrder() { return after; }
+    
+    private Set<String> before = new LinkedHashSet<String>();
+    public void addBeforeOrder(String fragmentName) {
+        before.add(fragmentName);
+    }
+    public void addBeforeOrderOthers() {
+        if (after.contains(ORDER_OTHERS)) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.multipleOther"));
+        }
+        before.add(ORDER_OTHERS);
+    }
+    public Set<String> getBeforeOrder() { return before; }
+
+    // Common elements and attributes
+    
+    // Required attribute of web-app element
+    private String version = null;
+    public String getVersion() { return version; }
+    public void setVersion(String version) { this.version = version; }
+
+    // Optional publicId attribute
+    private String publicId = null;
+    public String getPublicId() { return publicId; }
+    public void setPublicId(String publicId) { this.publicId = publicId; }
+    
+    // Optional metadata-complete attribute
+    private boolean metadataComplete = false;
+    public boolean isMetadataComplete() { return metadataComplete; }
+    public void setMetadataComplete(boolean metadataComplete) {
+        this.metadataComplete = metadataComplete; }
+    
+    // Optional name element
+    private String name = null;
+    public String getName() { return name; }
+    public void setName(String name) {
+        if (ORDER_OTHERS.equalsIgnoreCase(name)) {
+            // This is unusual. This name will be ignored. Log the fact.
+            log.warn(sm.getString("webXml.reservedName", name));
+        } else {
+            this.name = name;
+        }
+    }
+
+    // web-app elements
+    // TODO: Ignored elements:
+    // - description
+    // - icon
+
+    // display-name - TODO should support multiple with language
+    private String displayName = null;
+    public String getDisplayName() { return displayName; }
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+    
+    // distributable
+    private boolean distributable = false;
+    public boolean isDistributable() { return distributable; }
+    public void setDistributable(boolean distributable) {
+        this.distributable = distributable;
+    }
+    
+    // context-param
+    // TODO: description (multiple with language) is ignored
+    private Map<String,String> contextParams = new HashMap<String,String>();
+    public void addContextParam(String param, String value) {
+        contextParams.put(param, value);
+    }
+    public Map<String,String> getContextParams() { return contextParams; }
+    
+    // filter
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-name elements with language
+    // TODO: Should support multiple icon elements
+    // TODO: Description for init-param is ignored
+    private Map<String,FilterDef> filters =
+        new LinkedHashMap<String,FilterDef>();
+    public void addFilter(FilterDef filter) {
+        if (filters.containsKey(filter.getFilterName())) {
+            // Filter names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateFilter",
+                            filter.getFilterName()));
+        }
+        filters.put(filter.getFilterName(), filter);
+    }
+    public Map<String,FilterDef> getFilters() { return filters; }
+    
+    // filter-mapping
+    private Set<FilterMap> filterMaps = new LinkedHashSet<FilterMap>();
+    public void addFilterMapping(FilterMap filterMap) {
+        filterMaps.add(filterMap);
+    }
+    public Set<FilterMap> getFilterMappings() { return filterMaps; }
+    
+    // listener
+    // TODO: description (multiple with language) is ignored
+    // TODO: display-name (multiple with language) is ignored
+    // TODO: icon (multiple) is ignored
+    private Set<String> listeners = new LinkedHashSet<String>();
+    public void addListener(String className) {
+        listeners.add(className);
+    }
+    public Set<String> getListeners() { return listeners; }
+    
+    // servlet
+    // TODO: description (multiple with language) is ignored
+    // TODO: display-name (multiple with language) is ignored
+    // TODO: icon (multiple) is ignored
+    // TODO: init-param/description (multiple with language) is ignored
+    // TODO: security-role-ref/description (multiple with language) is ignored
+    private Map<String,ServletDef> servlets = new HashMap<String,ServletDef>();
+    public void addServlet(ServletDef servletDef) {
+        servlets.put(servletDef.getServletName(), servletDef);
+    }
+    public Map<String,ServletDef> getServlets() { return servlets; }
+    
+    // servlet-mapping
+    private Map<String,String> servletMappings = new HashMap<String,String>();
+    public void addServletMapping(String urlPattern, String servletName) {
+        servletMappings.put(urlPattern, servletName);
+    }
+    public Map<String,String> getServletMappings() { return servletMappings; }
+    
+    // session-config
+    // Digester will check there is only one of these
+    private SessionConfig sessionConfig = new SessionConfig();
+    public void setSessionConfig(SessionConfig sessionConfig) {
+        this.sessionConfig = sessionConfig;
+    }
+    public SessionConfig getSessionConfig() { return sessionConfig; }
+    
+    // mime-mapping
+    private Map<String,String> mimeMappings = new HashMap<String,String>();
+    public void addMimeMapping(String extension, String mimeType) {
+        mimeMappings.put(extension, mimeType);
+    }
+    public Map<String,String> getMimeMappings() { return mimeMappings; }
+    
+    // welcome-file-list
+    // When merging web.xml files it may be necessary for any new welcome files
+    // to completely replace the current set
+    private boolean replaceWelcomeFiles = false;
+    public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) {
+        this.replaceWelcomeFiles = replaceWelcomeFiles;
+    }
+    private Set<String> welcomeFiles = new LinkedHashSet<String>();
+    public void addWelcomeFile(String welcomeFile) {
+        if (replaceWelcomeFiles) {
+            welcomeFiles.clear();
+            replaceWelcomeFiles = false;
+        }
+        welcomeFiles.add(welcomeFile);
+    }
+    public Set<String> getWelcomeFiles() { return welcomeFiles; }
+    
+    // error-page
+    private Map<String,ErrorPage> errorPages = new HashMap<String,ErrorPage>();
+    public void addErrorPage(ErrorPage errorPage) {
+        errorPages.put(errorPage.getName(), errorPage);
+    }
+    public Map<String,ErrorPage> getErrorPages() { return errorPages; }
+    
+    // Digester will check there is only one jsp-config
+    // jsp-config/taglib or taglib (2.3 and earlier)
+    private Map<String,String> taglibs = new HashMap<String,String>();
+    public void addTaglib(String uri, String location) {
+        if (taglibs.containsKey(uri)) {
+            // Taglib URIs must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateTaglibUri", uri));
+        }
+        taglibs.put(uri, location);
+    }
+    public Map<String,String> getTaglibs() { return taglibs; }
+    
+    // jsp-config/jsp-property-group
+    private Set<JspPropertyGroup> jspPropertyGroups =
+        new HashSet<JspPropertyGroup>();
+    public void addJspPropertyGroup(JspPropertyGroup propertyGroup) {
+        jspPropertyGroups.add(propertyGroup);
+    }
+    public Set<JspPropertyGroup> getJspPropertyGroups() {
+        return jspPropertyGroups;
+    }
+
+    // security-constraint
+    // TODO: Should support multiple display-name elements with language
+    // TODO: Should support multiple description elements with language
+    private Set<SecurityConstraint> securityConstraints =
+        new HashSet<SecurityConstraint>();
+    public void addSecurityConstraint(SecurityConstraint securityConstraint) {
+        securityConstraints.add(securityConstraint);
+    }
+    public Set<SecurityConstraint> getSecurityConstraints() {
+        return securityConstraints;
+    }
+    
+    // login-config
+    // Digester will check there is only one of these
+    private LoginConfig loginConfig = null;
+    public void setLoginConfig(LoginConfig loginConfig) {
+        this.loginConfig = loginConfig;
+    }
+    public LoginConfig getLoginConfig() { return loginConfig; }
+    
+    // security-role
+    // TODO: description (multiple with language) is ignored
+    private Set<String> securityRoles = new HashSet<String>();
+    public void addSecurityRole(String securityRole) {
+        securityRoles.add(securityRole);
+    }
+    public Set<String> getSecurityRoles() { return securityRoles; }
+    
+    // env-entry
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextEnvironment> envEntries =
+        new HashMap<String,ContextEnvironment>();
+    public void addEnvEntry(ContextEnvironment envEntry) {
+        if (envEntries.containsKey(envEntry.getName())) {
+            // env-entry names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateEnvEntry",
+                            envEntry.getName()));
+        }
+        envEntries.put(envEntry.getName(),envEntry);
+    }
+    public Map<String,ContextEnvironment> getEnvEntries() { return envEntries; }
+    
+    // ejb-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextEjb> ejbRefs = new HashMap<String,ContextEjb>();
+    public void addEjbRef(ContextEjb ejbRef) {
+        ejbRefs.put(ejbRef.getName(),ejbRef);
+    }
+    public Map<String,ContextEjb> getEjbRefs() { return ejbRefs; }
+    
+    // ejb-local-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextLocalEjb> ejbLocalRefs =
+        new HashMap<String,ContextLocalEjb>();
+    public void addEjbLocalRef(ContextLocalEjb ejbLocalRef) {
+        ejbLocalRefs.put(ejbLocalRef.getName(),ejbLocalRef);
+    }
+    public Map<String,ContextLocalEjb> getEjbLocalRefs() {
+        return ejbLocalRefs;
+    }
+    
+    // service-ref
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-names elements with language
+    // TODO: Should support multiple icon elements ???
+    private Map<String,ContextService> serviceRefs =
+        new HashMap<String,ContextService>();
+    public void addServiceRef(ContextService serviceRef) {
+        serviceRefs.put(serviceRef.getName(), serviceRef);
+    }
+    public Map<String,ContextService> getServiceRefs() { return serviceRefs; }
+    
+    // resource-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextResource> resourceRefs =
+        new HashMap<String,ContextResource>();
+    public void addResourceRef(ContextResource resourceRef) {
+        if (resourceRefs.containsKey(resourceRef.getName())) {
+            // resource-ref names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateResourceRef",
+                            resourceRef.getName()));
+        }
+        resourceRefs.put(resourceRef.getName(), resourceRef);
+    }
+    public Map<String,ContextResource> getResourceRefs() {
+        return resourceRefs;
+    }
+    
+    // resource-env-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextResourceEnvRef> resourceEnvRefs =
+        new HashMap<String,ContextResourceEnvRef>();
+    public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
+        if (resourceEnvRefs.containsKey(resourceEnvRef.getName())) {
+            // resource-env-ref names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateResourceEnvRef",
+                            resourceEnvRef.getName()));
+        }
+        resourceEnvRefs.put(resourceEnvRef.getName(), resourceEnvRef);
+    }
+    public Map<String,ContextResourceEnvRef> getResourceEnvRefs() {
+        return resourceEnvRefs;
+    }
+    
+    // message-destination-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,MessageDestinationRef> messageDestinationRefs =
+        new HashMap<String,MessageDestinationRef>();
+    public void addMessageDestinationRef(
+            MessageDestinationRef messageDestinationRef) {
+        if (messageDestinationRefs.containsKey(
+                messageDestinationRef.getName())) {
+            // message-destination-ref names must be unique within a
+            // web(-fragment).xml
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.duplicateMessageDestinationRef",
+                    messageDestinationRef.getName()));
+        }
+        messageDestinationRefs.put(messageDestinationRef.getName(),
+                messageDestinationRef);
+    }
+    public Map<String,MessageDestinationRef> getMessageDestinationRefs() {
+        return messageDestinationRefs;
+    }
+    
+    // message-destination
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-names elements with language
+    // TODO: Should support multiple icon elements ???
+    private Map<String,MessageDestination> messageDestinations =
+        new HashMap<String,MessageDestination>();
+    public void addMessageDestination(
+            MessageDestination messageDestination) {
+        if (messageDestinations.containsKey(
+                messageDestination.getName())) {
+            // message-destination names must be unique within a
+            // web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateMessageDestination",
+                            messageDestination.getName()));
+        }
+        messageDestinations.put(messageDestination.getName(),
+                messageDestination);
+    }
+    public Map<String,MessageDestination> getMessageDestinations() {
+        return messageDestinations;
+    }
+    
+    // locale-encoging-mapping-list
+    private Map<String,String> localeEncodingMappings =
+        new HashMap<String,String>();
+    public void addLocaleEncodingMapping(String locale, String encoding) {
+        localeEncodingMappings.put(locale, encoding);
+    }
+    public Map<String,String> getLocalEncodingMappings() {
+        return localeEncodingMappings;
+    }
+    
+
+    // Attributes not defined in web.xml or web-fragment.xml
+    
+    // URL of JAR / exploded JAR for this web-fragment
+    private URL uRL = null;
+    public void setURL(URL url) { this.uRL = url; }
+    public URL getURL() { return uRL; }
+    
+    
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(32);
+        buf.append("Name: ");
+        buf.append(getName());
+        buf.append(", URL: ");
+        buf.append(getURL());
+        return buf.toString();
+    }
+    
+    private static final String INDENT2 = "  ";
+    private static final String INDENT4 = "    ";
+    private static final String INDENT6 = "      ";
+    
+    /**
+     * Generate a web.xml in String form that matches the representation stored
+     * in this object.
+     * 
+     * @return The complete contents of web.xml as a String
+     */
+    public String toXml() {
+        StringBuilder sb = new StringBuilder(2048);
+        
+        // TODO - Various, icon, description etc elements are skipped - mainly
+        //        because they are ignored when web.xml is parsed - see above
+
+        // Declaration
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+        
+        // Root element
+        sb.append("<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n");
+        sb.append("         xmlns:xsi=");
+        sb.append("\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+        sb.append("         xsi:schemaLocation=");
+        sb.append("\"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"\n");
+        sb.append("         version=\"");
+        if (version != null) {
+            sb.append(version);
+        } else {
+            // Should be non-null but in case it isn't assume 3.0
+            sb.append("3.0");
+        }
+        sb.append("\"\n");
+        sb.append("         metadata-complete=\"true\">\n\n");
+
+        appendElement(sb, INDENT2, "display-name", displayName);
+        
+        if (isDistributable()) {
+            sb.append("  <distributable/>\n\n");
+        }
+        
+        for (Map.Entry<String, String> entry : contextParams.entrySet()) {
+            sb.append("  <context-param>\n");
+            appendElement(sb, INDENT4, "param-name", entry.getKey());
+            appendElement(sb, INDENT4, "param-valuee", entry.getValue());
+            sb.append("  </context-param>\n");
+        }
+        sb.append('\n');
+        
+        for (Map.Entry<String, FilterDef> entry : filters.entrySet()) {
+            FilterDef filterDef = entry.getValue();
+            sb.append("  <filter>\n");
+            appendElement(sb, INDENT4, "description",
+                    filterDef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    filterDef.getDisplayName());
+            appendElement(sb, INDENT4, "filter-name",
+                    filterDef.getFilterName());
+            appendElement(sb, INDENT4, "filter-class",
+                    filterDef.getFilterClass());
+            appendElement(sb, INDENT4, "async-supported",
+                    filterDef.getAsyncSupported());
+            for (Map.Entry<String, String> param :
+                    filterDef.getParameterMap().entrySet()) {
+                sb.append("    <init-param>\n");
+                appendElement(sb, INDENT6, "param-name", param.getKey());
+                appendElement(sb, INDENT6, "param-value", param.getValue());
+                sb.append("    </init-param>\n");
+            }
+            sb.append("  </filter>\n");
+        }
+        sb.append('\n');
+
+        for (FilterMap filterMap : filterMaps) {
+            sb.append("  <filter-mapping>\n");
+            appendElement(sb, INDENT4, "filter-name",
+                    filterMap.getFilterName());
+            if (filterMap.getMatchAllServletNames()) {
+                sb.append("    <servlet-name>*</servlet-name>\n");
+            } else {
+                for (String servletName : filterMap.getServletNames()) {
+                    appendElement(sb, INDENT4, "servlet-name", servletName);
+                }
+            }
+            if (filterMap.getMatchAllUrlPatterns()) {
+                sb.append("    <url-pattern>*</url-pattern>\n");
+            } else {
+                for (String urlPattern : filterMap.getURLPatterns()) {
+                    appendElement(sb, INDENT4, "url-pattern", urlPattern);
+                }
+            }
+            for (String dispatcher : filterMap.getDispatcherNames()) {
+                appendElement(sb, INDENT4, "dispatcher", dispatcher);
+            }
+            sb.append("  </filter-mapping>\n");
+        }
+        sb.append('\n');
+
+        for (String listener : listeners) {
+            sb.append("  <listener>\n");
+            appendElement(sb, INDENT4, "listener-class", listener);
+            sb.append("  </listener>\n");
+        }
+        sb.append('\n');
+
+        for (Map.Entry<String, ServletDef> entry : servlets.entrySet()) {
+            ServletDef servletDef = entry.getValue();
+            sb.append("  <servlet>\n");
+            appendElement(sb, INDENT4, "description",
+                    servletDef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    servletDef.getDisplayName());
+            appendElement(sb, INDENT4, "servlet-name", entry.getKey());
+            appendElement(sb, INDENT4, "servlet-class",
+                    servletDef.getServletClass());
+            appendElement(sb, INDENT4, "jsp-file", servletDef.getJspFile());
+            for (Map.Entry<String, String> param :
+                    servletDef.getParameterMap().entrySet()) {
+                sb.append("    <init-param>\n");
+                appendElement(sb, INDENT6, "param-name", param.getKey());
+                appendElement(sb, INDENT6, "param-value", param.getValue());
+                sb.append("    </init-param>\n");
+            }
+            appendElement(sb, INDENT4, "load-on-startup",
+                    servletDef.getLoadOnStartup());
+            appendElement(sb, INDENT4, "enabled", servletDef.getEnabled());
+            appendElement(sb, INDENT4, "async-supported",
+                    servletDef.getAsyncSupported());
+            if (servletDef.getRunAs() != null) {
+                sb.append("    <run-as>\n");
+                appendElement(sb, INDENT6, "role-name", servletDef.getRunAs());
+                sb.append("    </run-as>\n");
+            }
+            for (SecurityRoleRef roleRef : servletDef.getSecurityRoleRefs()) {
+                sb.append("    <security-role-ref>\n");
+                appendElement(sb, INDENT6, "role-name", roleRef.getName());
+                appendElement(sb, INDENT6, "role-link", roleRef.getLink());
+                sb.append("    </security-role-ref>\n");
+            }
+            MultipartDef multipartDef = servletDef.getMultipartDef();
+            if (multipartDef != null) {
+                sb.append("    <multipart-config>\n");
+                appendElement(sb, INDENT6, "location",
+                        multipartDef.getLocation());
+                appendElement(sb, INDENT6, "max-file-size",
+                        multipartDef.getMaxFileSize());
+                appendElement(sb, INDENT6, "max-request-size",
+                        multipartDef.getMaxRequestSize());
+                appendElement(sb, INDENT6, "file-size-threshold",
+                        multipartDef.getFileSizeThreshold());
+                sb.append("    </multipart-config>\n");
+            }
+            sb.append("  </servlet>\n");
+        }
+        sb.append('\n');
+
+        for (Map.Entry<String, String> entry : servletMappings.entrySet()) {
+            sb.append("  <servlet-mapping>\n");
+            appendElement(sb, INDENT4, "servlet-name", entry.getValue());
+            appendElement(sb, INDENT4, "url-pattern", entry.getKey());
+            sb.append("  </servlet-mapping>\n");
+        }
+        sb.append('\n');
+        
+        if (sessionConfig != null) {
+            sb.append("  <session-config>\n");
+            appendElement(sb, INDENT4, "session-timeout",
+                    sessionConfig.getSessionTimeout());
+            sb.append("    <cookie-config>\n");
+            appendElement(sb, INDENT6, "name", sessionConfig.getCookieName());
+            appendElement(sb, INDENT6, "domain",
+                    sessionConfig.getCookieDomain());
+            appendElement(sb, INDENT6, "path", sessionConfig.getCookiePath());
+            appendElement(sb, INDENT6, "comment",
+                    sessionConfig.getCookieComment());
+            appendElement(sb, INDENT6, "http-only",
+                    sessionConfig.getCookieHttpOnly());
+            appendElement(sb, INDENT6, "secure",
+                    sessionConfig.getCookieSecure());
+            appendElement(sb, INDENT6, "max-age",
+                    sessionConfig.getCookieMaxAge());
+            sb.append("    </cookie-config>\n");
+            for (SessionTrackingMode stm :
+                    sessionConfig.getSessionTrackingModes()) {
+                appendElement(sb, INDENT4, "tracking-mode", stm.name());
+            }
+            sb.append("  </session-config>\n\n");
+        }
+        
+        for (Map.Entry<String, String> entry : mimeMappings.entrySet()) {
+            sb.append("  <mime-mapping>\n");
+            appendElement(sb, INDENT4, "extension", entry.getKey());
+            appendElement(sb, INDENT4, "mime-type", entry.getValue());
+            sb.append("  </mime-mapping>\n");
+        }
+        sb.append('\n');
+        
+        if (welcomeFiles.size() > 0) {
+            sb.append("  <welcome-file-list>\n");
+            for (String welcomeFile : welcomeFiles) {
+                appendElement(sb, INDENT4, "welcome-file", welcomeFile);
+            }
+            sb.append("  </welcome-file-list>\n\n");
+        }
+        
+        for (ErrorPage errorPage : errorPages.values()) {
+            sb.append("  <error-page>\n");
+            if (errorPage.getExceptionType() == null) {
+                appendElement(sb, INDENT4, "error-code",
+                        Integer.toString(errorPage.getErrorCode()));
+            } else {
+                appendElement(sb, INDENT4, "exception-type",
+                        errorPage.getExceptionType());
+            }
+            appendElement(sb, INDENT4, "location", errorPage.getLocation());
+            sb.append("  </error-page>\n");
+        }
+        sb.append('\n');
+
+        if (taglibs.size() > 0 || jspPropertyGroups.size() > 0) {
+            sb.append("  <jsp-config>\n");
+            for (Map.Entry<String, String> entry : taglibs.entrySet()) {
+                sb.append("    <taglib>\n");
+                appendElement(sb, INDENT6, "taglib-uri", entry.getKey());
+                appendElement(sb, INDENT6, "taglib-location", entry.getValue());
+                sb.append("    </taglib>\n");
+            }
+            for (JspPropertyGroup jpg : jspPropertyGroups) {
+                sb.append("    <jsp-property-group>\n");
+                appendElement(sb, INDENT6, "url-pattern", jpg.getUrlPattern());
+                appendElement(sb, INDENT6, "el-ignored", jpg.getElIgnored());
+                appendElement(sb, INDENT6, "scripting-invalid",
+                        jpg.getScriptingInvalid());
+                appendElement(sb, INDENT6, "page-encoding",
+                        jpg.getPageEncoding());
+                for (String prelude : jpg.getIncludePreludes()) {
+                    appendElement(sb, INDENT6, "include-prelude", prelude);
+                }
+                for (String coda : jpg.getIncludeCodas()) {
+                    appendElement(sb, INDENT6, "include-coda", coda);
+                }
+                appendElement(sb, INDENT6, "is-xml", jpg.getIsXml());
+                appendElement(sb, INDENT6, "deferred-syntax-allowed-as-literal",
+                        jpg.getDeferredSyntax());
+                appendElement(sb, INDENT6, "trim-directive-whitespaces",
+                        jpg.getTrimWhitespace());
+                appendElement(sb, INDENT6, "default-content-type",
+                        jpg.getDefaultContentType());
+                appendElement(sb, INDENT6, "buffer", jpg.getBuffer());
+                appendElement(sb, INDENT6, "error-on-undeclared-namespace",
+                        jpg.getErrorOnUndeclaredNamespace());
+                sb.append("    </jsp-property-group>\n");
+            }
+            sb.append("  </jsp-config>\n\n");
+        }
+        
+        for (SecurityConstraint constraint : securityConstraints) {
+            sb.append("  <security-constraint>\n");
+            appendElement(sb, INDENT4, "display-name",
+                    constraint.getDisplayName());
+            for (SecurityCollection collection : constraint.findCollections()) {
+                sb.append("    <web-resource-collection>\n");
+                appendElement(sb, INDENT6, "web-resource-name",
+                        collection.getName());
+                appendElement(sb, INDENT6, "description",
+                        collection.getDescription());
+                for (String urlPattern : collection.findPatterns()) {
+                    appendElement(sb, INDENT6, "url-pattern", urlPattern);
+                }
+                for (String method : collection.findMethods()) {
+                    appendElement(sb, INDENT6, "http-method", method);
+                }
+                for (String method : collection.findOmittedMethods()) {
+                    appendElement(sb, INDENT6, "http-method-omission", method);
+                }
+                sb.append("    </web-resource-collection>\n");
+            }
+            if (constraint.findAuthRoles().length > 0) {
+                sb.append("    <auth-constraint>\n");
+                for (String role : constraint.findAuthRoles()) {
+                    appendElement(sb, INDENT6, "role-name", role);
+                }
+                sb.append("    </auth-constraint>\n");
+            }
+            if (constraint.getUserConstraint() != null) {
+                sb.append("    <user-data-constraint>\n");
+                appendElement(sb, INDENT6, "transport-guarantee",
+                        constraint.getUserConstraint());
+                sb.append("    </user-data-constraint>\n");
+            }
+            sb.append("  </security-constraint>\n");
+        }
+        sb.append('\n');
+
+        if (loginConfig != null) {
+            sb.append("  <login-config>\n");
+            appendElement(sb, INDENT4, "auth-method",
+                    loginConfig.getAuthMethod());
+            appendElement(sb,INDENT4, "realm-name",
+                    loginConfig.getRealmName());
+            if (loginConfig.getErrorPage() != null ||
+                        loginConfig.getLoginPage() != null) {
+                sb.append("    <form-login-config>\n");
+                appendElement(sb, INDENT6, "form-login-page",
+                        loginConfig.getLoginPage());
+                appendElement(sb, INDENT6, "form-error-page",
+                        loginConfig.getErrorPage());
+                sb.append("    </form-login-config>\n");
+            }
+            sb.append("  </login-config>\n\n");
+        }
+        
+        for (String roleName : securityRoles) {
+            sb.append("  <security-role>\n");
+            appendElement(sb, INDENT4, "role-name", roleName);
+            sb.append("  </security-role>\n");
+        }
+        
+        for (ContextEnvironment envEntry : envEntries.values()) {
+            sb.append("  <env-entry>\n");
+            appendElement(sb, INDENT4, "description",
+                    envEntry.getDescription());
+            appendElement(sb, INDENT4, "env-entry-name", envEntry.getName());
+            appendElement(sb, INDENT4, "env-entry-type", envEntry.getType());
+            appendElement(sb, INDENT4, "env-entry-value", envEntry.getValue());
+            // TODO mapped-name
+            for (InjectionTarget target : envEntry.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </env-entry>\n");
+        }
+        sb.append('\n');
+
+        for (ContextEjb ejbRef : ejbRefs.values()) {
+            sb.append("  <ejb-ref>\n");
+            appendElement(sb, INDENT4, "description", ejbRef.getDescription());
+            appendElement(sb, INDENT4, "ejb-ref-name", ejbRef.getName());
+            appendElement(sb, INDENT4, "ejb-ref-type", ejbRef.getType());
+            appendElement(sb, INDENT4, "home", ejbRef.getHome());
+            appendElement(sb, INDENT4, "remote", ejbRef.getRemote());
+            appendElement(sb, INDENT4, "ejb-link", ejbRef.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : ejbRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </ejb-ref>\n");
+        }
+        sb.append('\n');
+
+        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
+            sb.append("  <ejb-local-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    ejbLocalRef.getDescription());
+            appendElement(sb, INDENT4, "ejb-ref-name", ejbLocalRef.getName());
+            appendElement(sb, INDENT4, "ejb-ref-type", ejbLocalRef.getType());
+            appendElement(sb, INDENT4, "local-home", ejbLocalRef.getHome());
+            appendElement(sb, INDENT4, "local", ejbLocalRef.getLocal());
+            appendElement(sb, INDENT4, "ejb-link", ejbLocalRef.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : ejbLocalRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </ejb-local-ref>\n");
+        }
+        sb.append('\n');
+        
+        for (ContextService serviceRef : serviceRefs.values()) {
+            sb.append("  <service-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    serviceRef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    serviceRef.getDisplayname());
+            appendElement(sb, INDENT4, "service-ref-name",
+                    serviceRef.getName());
+            appendElement(sb, INDENT4, "service-interface",
+                    serviceRef.getInterface());
+            appendElement(sb, INDENT4, "service-ref-type",
+                    serviceRef.getType());
+            appendElement(sb, INDENT4, "wsdl-file", serviceRef.getWsdlfile());
+            appendElement(sb, INDENT4, "jaxrpc-mapping-file",
+                    serviceRef.getJaxrpcmappingfile());
+            String qname = serviceRef.getServiceqnameNamespaceURI();
+            if (qname != null) {
+                qname = qname + ":";
+            }
+            qname = qname + serviceRef.getServiceqnameLocalpart();
+            appendElement(sb, INDENT4, "service-qname", qname);
+            Iterator<String> endpointIter = serviceRef.getServiceendpoints();
+            while (endpointIter.hasNext()) {
+                String endpoint = endpointIter.next();
+                sb.append("    <port-component-ref>\n");
+                appendElement(sb, INDENT6, "service-endpoint-interface",
+                        endpoint);
+                appendElement(sb, INDENT6, "port-component-link",
+                        serviceRef.getProperty(endpoint));
+                sb.append("    </port-component-ref>\n");
+            }
+            Iterator<String> handlerIter = serviceRef.getHandlers();
+            while (handlerIter.hasNext()) {
+                String handler = handlerIter.next();
+                sb.append("    <handler>\n");
+                ContextHandler ch = serviceRef.getHandler(handler);
+                appendElement(sb, INDENT6, "handler-name", ch.getName());
+                appendElement(sb, INDENT6, "handler-class",
+                        ch.getHandlerclass());
+                sb.append("    </handler>\n");
+            }
+            // TODO handler-chains
+            // TODO mapped-name
+            for (InjectionTarget target : serviceRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </service-ref>\n");
+        }
+        sb.append('\n');
+        
+        for (ContextResource resourceRef : resourceRefs.values()) {
+            sb.append("  <resource-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    resourceRef.getDescription());
+            appendElement(sb, INDENT4, "res-ref-name", resourceRef.getName());
+            appendElement(sb, INDENT4, "res-type", resourceRef.getType());
+            appendElement(sb, INDENT4, "res-auth", resourceRef.getAuth());
+            appendElement(sb, INDENT4, "res-sharing-scope",
+                    resourceRef.getScope());
+            // TODO mapped-name
+            for (InjectionTarget target : resourceRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </resource-ref>\n");
+        }
+        sb.append('\n');
+
+        for (ContextResourceEnvRef resourceEnvRef : resourceEnvRefs.values()) {
+            sb.append("  <resource-env-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    resourceEnvRef.getDescription());
+            appendElement(sb, INDENT4, "resource-env-ref-name",
+                    resourceEnvRef.getName());
+            appendElement(sb, INDENT4, "resource-env-ref-type",
+                    resourceEnvRef.getType());
+            // TODO mapped-name
+            for (InjectionTarget target :
+                    resourceEnvRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </resource-env-ref>\n");
+        }
+        sb.append('\n');
+
+        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
+            sb.append("  <message-destination-ref>\n");
+            appendElement(sb, INDENT4, "description", mdr.getDescription());
+            appendElement(sb, INDENT4, "message-destination-ref-name",
+                    mdr.getName());
+            appendElement(sb, INDENT4, "message-destination-type",
+                    mdr.getType());
+            appendElement(sb, INDENT4, "message-destination-usage",
+                    mdr.getUsage());
+            appendElement(sb, INDENT4, "message-destination-link",
+                    mdr.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : mdr.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </message-destination-ref>\n");
+        }
+        sb.append('\n');
+
+        for (MessageDestination md : messageDestinations.values()) {
+            sb.append("  <message-destination>\n");
+            appendElement(sb, INDENT4, "description", md.getDescription());
+            appendElement(sb, INDENT4, "display-name", md.getDisplayName());
+            appendElement(sb, INDENT4, "message-destination-name",
+                    md.getName());
+            // TODO mapped-name
+            sb.append("  </message-destination>\n");
+        }
+        sb.append('\n');
+
+        if (localeEncodingMappings.size() > 0) {
+            sb.append("  <locale-encoding-mapping-list>\n");
+            for (Map.Entry<String, String> entry :
+                    localeEncodingMappings.entrySet()) {
+                sb.append("    <locale-encoding-mapping>\n");
+                appendElement(sb, INDENT6, "locale", entry.getKey());
+                appendElement(sb, INDENT6, "encoding", entry.getValue());
+                sb.append("    </locale-encoding-mapping>\n");
+            }
+            sb.append("  </locale-encoding-mapping-list>\n");
+        }
+        sb.append("</web-app>");
+        return sb.toString();
+    }
+
+    private static void appendElement(StringBuilder sb, String indent,
+            String elementName, String value) {
+        if (value == null || value.length() == 0) return;
+        sb.append(indent);
+        sb.append('<');
+        sb.append(elementName);
+        sb.append('>');
+        sb.append(escapeXml(value));
+        sb.append("</");
+        sb.append(elementName);
+        sb.append(">\n");
+    }
+
+    private static void appendElement(StringBuilder sb, String indent,
+            String elementName, Object value) {
+        if (value == null) return;
+        appendElement(sb, indent, elementName, value.toString());
+    }
+
+
+    /**
+     * Escape the 5 entities defined by XML.
+     */
+    private static String escapeXml(String s) {
+        if (s == null)
+            return null;
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '<') {
+                sb.append("&lt;");
+            } else if (c == '>') {
+                sb.append("&gt;");
+            } else if (c == '\'') {
+                sb.append("&apos;");
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&quot;");
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Configure a {@link Context} using the stored web.xml representation.
+     *  
+     * @param context   The context to be configured
+     */
+    public void configureContext(Context context) {
+        // As far as possible, process in alphabetical order so it is easy to
+        // check everything is present
+        // Some validation depends on correct public ID
+        context.setPublicId(publicId);
+
+        // Everything else in order
+        for (String contextParam : contextParams.keySet()) {
+            context.addParameter(contextParam, contextParams.get(contextParam));
+        }
+        context.setDisplayName(displayName);
+        context.setDistributable(distributable);
+        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
+            context.getNamingResources().addLocalEjb(ejbLocalRef);
+        }
+        for (ContextEjb ejbRef : ejbRefs.values()) {
+            context.getNamingResources().addEjb(ejbRef);
+        }
+        for (ContextEnvironment environment : envEntries.values()) {
+            context.getNamingResources().addEnvironment(environment);
+        }
+        for (ErrorPage errorPage : errorPages.values()) {
+            context.addErrorPage(errorPage);
+        }
+        for (FilterDef filter : filters.values()) {
+            context.addFilterDef(filter);
+        }
+        for (FilterMap filterMap : filterMaps) {
+            context.addFilterMap(filterMap);
+        }
+        // jsp-property-group needs to be after servlet configuration
+        for (String listener : listeners) {
+            context.addApplicationListener(listener);
+        }
+        for (String locale : localeEncodingMappings.keySet()) {
+            context.addLocaleEncodingMappingParameter(locale,
+                    localeEncodingMappings.get(locale));
+        }
+        // Prevents IAE
+        if (loginConfig != null) {
+            context.setLoginConfig(loginConfig);
+        }
+        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
+            context.getNamingResources().addMessageDestinationRef(mdr);
+        }
+
+        // messageDestinations were ignored in Tomcat 6, so ignore here
+        
+        context.setIgnoreAnnotations(metadataComplete);
+        for (String extension : mimeMappings.keySet()) {
+            context.addMimeMapping(extension, mimeMappings.get(extension));
+        }
+        // Name is just used for ordering
+        for (ContextResourceEnvRef resource : resourceEnvRefs.values()) {
+            context.getNamingResources().addResourceEnvRef(resource);
+        }
+        for (ContextResource resource : resourceRefs.values()) {
+            context.getNamingResources().addResource(resource);
+        }
+        for (SecurityConstraint constraint : securityConstraints) {
+            context.addConstraint(constraint);
+        }
+        for (String role : securityRoles) {
+            context.addSecurityRole(role);
+        }
+        for (ContextService service : serviceRefs.values()) {
+            context.getNamingResources().addService(service);
+        }
+        for (ServletDef servlet : servlets.values()) {
+            Wrapper wrapper = context.createWrapper();
+            // Description is ignored
+            // Display name is ignored
+            // Icons are ignored
+            // Only set this if it is non-null else every servlet will get
+            // marked as the JSP servlet
+            String jspFile = servlet.getJspFile();
+            if (jspFile != null) {
+                wrapper.setJspFile(jspFile);
+            }
+            if (servlet.getLoadOnStartup() != null) {
+                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
+            }
+            if (servlet.getEnabled() != null) {
+                wrapper.setEnabled(servlet.getEnabled().booleanValue());
+            }
+            wrapper.setName(servlet.getServletName());
+            Map<String,String> params = servlet.getParameterMap(); 
+            for (String param : params.keySet()) {
+                wrapper.addInitParameter(param, params.get(param));
+            }
+            wrapper.setRunAs(servlet.getRunAs());
+            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
+            for (SecurityRoleRef roleRef : roleRefs) {
+                wrapper.addSecurityReference(
+                        roleRef.getName(), roleRef.getLink());
+            }
+            wrapper.setServletClass(servlet.getServletClass());
+            MultipartDef multipartdef = servlet.getMultipartDef();
+            if (multipartdef != null) {
+                if (multipartdef.getMaxFileSize() != null &&
+                        multipartdef.getMaxRequestSize()!= null &&
+                        multipartdef.getFileSizeThreshold() != null) {
+                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
+                            multipartdef.getLocation(),
+                            Long.parseLong(multipartdef.getMaxFileSize()),
+                            Long.parseLong(multipartdef.getMaxRequestSize()),
+                            Integer.parseInt(
+                                    multipartdef.getFileSizeThreshold())));
+                } else {
+                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
+                            multipartdef.getLocation()));
+                }
+            }
+            if (servlet.getAsyncSupported() != null) {
+                wrapper.setAsyncSupported(
+                        servlet.getAsyncSupported().booleanValue());
+            }
+            context.addChild(wrapper);
+        }
+        for (String pattern : servletMappings.keySet()) {
+            context.addServletMapping(pattern, servletMappings.get(pattern));
+        }
+        if (sessionConfig != null) {
+            if (sessionConfig.getSessionTimeout() != null) {
+                context.setSessionTimeout(
+                        sessionConfig.getSessionTimeout().intValue());
+            }
+            SessionCookieConfig scc =
+                context.getServletContext().getSessionCookieConfig();
+            scc.setName(sessionConfig.getCookieName());
+            scc.setDomain(sessionConfig.getCookieDomain());
+            scc.setPath(sessionConfig.getCookiePath());
+            scc.setComment(sessionConfig.getCookieComment());
+            if (sessionConfig.getCookieHttpOnly() != null) {
+                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
+            }
+            if (sessionConfig.getCookieSecure() != null) {
+                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
+            }
+            if (sessionConfig.getCookieMaxAge() != null) {
+                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
+            }
+            if (sessionConfig.getSessionTrackingModes().size() > 0) {
+                context.getServletContext().setSessionTrackingModes(
+                        sessionConfig.getSessionTrackingModes());
+            }
+        }
+        for (String uri : taglibs.keySet()) {
+            context.addTaglib(uri, taglibs.get(uri));
+        }
+        
+        // Context doesn't use version directly
+        
+        for (String welcomeFile : welcomeFiles) {
+            context.addWelcomeFile(welcomeFile);
+        }
+
+        // Do this last as it depends on servlets
+        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
+            context.addJspMapping(jspPropertyGroup.getUrlPattern());
+        }
+    }
+    
+    /**
+     * Merge the supplied web fragments into this main web.xml.
+     * 
+     * @param fragments     The fragments to merge in
+     * @return <code>true</code> if merge is successful, else
+     *         <code>false</code>
+     */
+    public boolean merge(Set<WebXml> fragments) {
+        // As far as possible, process in alphabetical order so it is easy to
+        // check everything is present
+        
+        // Merge rules vary from element to element. See SRV.8.2.3
+
+        WebXml temp = new WebXml();
+        Map<String,Boolean> mergeInjectionFlags =
+            new HashMap<String, Boolean>();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getContextParams(), contextParams,
+                    temp.getContextParams(), fragment, "Context Parameter")) {
+                return false;
+            }
+        }
+        contextParams.putAll(temp.getContextParams());
+
+        if (displayName == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getDisplayName(); 
+                if (value != null) {
+                    if (temp.getDisplayName() == null) {
+                        temp.setDisplayName(value);
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictDisplayName",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            displayName = temp.getDisplayName();
+        }
+
+        if (distributable) {
+            for (WebXml fragment : fragments) {
+                if (!fragment.isDistributable()) {
+                    distributable = false;
+                    break;
+                }
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEjbLocalRefs(), ejbLocalRefs,
+                    temp.getEjbLocalRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        ejbLocalRefs.putAll(temp.getEjbLocalRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEjbRefs(), ejbRefs,
+                    temp.getEjbRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        ejbRefs.putAll(temp.getEjbRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEnvEntries(), envEntries,
+                    temp.getEnvEntries(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        envEntries.putAll(temp.getEnvEntries());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getErrorPages(), errorPages,
+                    temp.getErrorPages(), fragment, "Error Page")) {
+                return false;
+            }
+        }
+        errorPages.putAll(temp.getErrorPages());
+
+        for (WebXml fragment : fragments) {
+            for (FilterMap filterMap : fragment.getFilterMappings()) {
+                // Always additive
+                addFilterMapping(filterMap);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,FilterDef> entry :
+                    fragment.getFilters().entrySet()) {
+                if (filters.containsKey(entry.getKey())) {
+                    mergeFilter(entry.getValue(),
+                            filters.get(entry.getKey()), false);
+                } else {
+                    if (temp.getFilters().containsKey(entry.getKey())) {
+                        if (!(mergeFilter(entry.getValue(),
+                                temp.getFilters().get(entry.getKey()), true))) {
+                            log.error(sm.getString(
+                                    "webXml.mergeConflictFilter",
+                                    entry.getKey(),
+                                    fragment.getName(),
+                                    fragment.getURL()));
+    
+                            return false;
+                        }
+                    } else {
+                        temp.getFilters().put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        filters.putAll(temp.getFilters());
+
+        for (WebXml fragment : fragments) {
+            for (JspPropertyGroup jspPropertyGroup :
+                    fragment.getJspPropertyGroups()) {
+                // Always additive
+                addJspPropertyGroup(jspPropertyGroup);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (String listener : fragment.getListeners()) {
+                // Always additive
+                addListener(listener);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getLocalEncodingMappings(),
+                    localeEncodingMappings, temp.getLocalEncodingMappings(),
+                    fragment, "Locale Encoding Mapping")) {
+                return false;
+            }
+        }
+        localeEncodingMappings.putAll(temp.getLocalEncodingMappings());
+
+        if (getLoginConfig() == null) {
+            LoginConfig tempLoginConfig = null;
+            for (WebXml fragment : fragments) {
+                LoginConfig fragmentLoginConfig = fragment.loginConfig;
+                if (fragmentLoginConfig != null) {
+                    if (tempLoginConfig == null ||
+                            fragmentLoginConfig.equals(tempLoginConfig)) {
+                        tempLoginConfig = fragmentLoginConfig;
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictLoginConfig",
+                                fragment.getName(),
+                                fragment.getURL()));
+                    }
+                }
+            }
+            loginConfig = tempLoginConfig;
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getMessageDestinationRefs(), messageDestinationRefs,
+                    temp.getMessageDestinationRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        messageDestinationRefs.putAll(temp.getMessageDestinationRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getMessageDestinations(), messageDestinations,
+                    temp.getMessageDestinations(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        messageDestinations.putAll(temp.getMessageDestinations());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getMimeMappings(), mimeMappings,
+                    temp.getMimeMappings(), fragment, "Mime Mapping")) {
+                return false;
+            }
+        }
+        mimeMappings.putAll(temp.getMimeMappings());
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getResourceEnvRefs(), resourceEnvRefs,
+                    temp.getResourceEnvRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        resourceEnvRefs.putAll(temp.getResourceEnvRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getResourceRefs(), resourceRefs,
+                    temp.getResourceRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        resourceRefs.putAll(temp.getResourceRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            for (SecurityConstraint constraint : fragment.getSecurityConstraints()) {
+                // Always additive
+                addSecurityConstraint(constraint);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (String role : fragment.getSecurityRoles()) {
+                // Always additive
+                addSecurityRole(role);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getServiceRefs(), serviceRefs,
+                    temp.getServiceRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        serviceRefs.putAll(temp.getServiceRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,String> mapping :
+                    fragment.getServletMappings().entrySet()) {
+                // Always additive
+                addServletMapping(mapping.getKey(), mapping.getValue());
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,ServletDef> entry :
+                    fragment.getServlets().entrySet()) {
+                if (servlets.containsKey(entry.getKey())) {
+                    mergeServlet(entry.getValue(),
+                            servlets.get(entry.getKey()), false);
+                } else {
+                    if (temp.getServlets().containsKey(entry.getKey())) {
+                        if (!(mergeServlet(entry.getValue(),
+                                temp.getServlets().get(entry.getKey()), true))) {
+                            log.error(sm.getString(
+                                    "webXml.mergeConflictServlet",
+                                    entry.getKey(),
+                                    fragment.getName(),
+                                    fragment.getURL()));
+    
+                            return false;
+                        }
+                    } else {
+                        temp.getServlets().put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        servlets.putAll(temp.getServlets());
+        
+        if (sessionConfig.getSessionTimeout() == null) {
+            for (WebXml fragment : fragments) {
+                Integer value = fragment.getSessionConfig().getSessionTimeout();
+                if (value != null) {
+                    if (temp.getSessionConfig().getSessionTimeout() == null) {
+                        temp.getSessionConfig().setSessionTimeout(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getSessionTimeout())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionTimeout",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getSessionTimeout() != null) {
+                sessionConfig.setSessionTimeout(
+                        temp.getSessionConfig().getSessionTimeout().toString());
+            }
+        }
+        
+        if (sessionConfig.getCookieName() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieName();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieName() == null) {
+                        temp.getSessionConfig().setCookieName(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieName())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieName",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieName(
+                    temp.getSessionConfig().getCookieName());
+        }
+        if (sessionConfig.getCookieDomain() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieDomain();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieDomain() == null) {
+                        temp.getSessionConfig().setCookieDomain(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieDomain())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieDomain",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieDomain(
+                    temp.getSessionConfig().getCookieDomain());
+        }
+        if (sessionConfig.getCookiePath() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookiePath();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookiePath() == null) {
+                        temp.getSessionConfig().setCookiePath(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookiePath())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookiePath",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookiePath(
+                    temp.getSessionConfig().getCookiePath());
+        }
+        if (sessionConfig.getCookieComment() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieComment();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieComment() == null) {
+                        temp.getSessionConfig().setCookieComment(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieComment())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieComment",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieComment(
+                    temp.getSessionConfig().getCookieComment());
+        }
+        if (sessionConfig.getCookieHttpOnly() == null) {
+            for (WebXml fragment : fragments) {
+                Boolean value = fragment.getSessionConfig().getCookieHttpOnly();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieHttpOnly() == null) {
+                        temp.getSessionConfig().setCookieHttpOnly(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieHttpOnly())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieHttpOnly",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieHttpOnly() != null) {
+                sessionConfig.setCookieHttpOnly(
+                        temp.getSessionConfig().getCookieHttpOnly().toString());
+            }
+        }
+        if (sessionConfig.getCookieSecure() == null) {
+            for (WebXml fragment : fragments) {
+                Boolean value = fragment.getSessionConfig().getCookieSecure();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieSecure() == null) {
+                        temp.getSessionConfig().setCookieSecure(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieSecure())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieSecure",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieSecure() != null) {
+                sessionConfig.setCookieSecure(
+                        temp.getSessionConfig().getCookieSecure().toString());
+            }
+        }
+        if (sessionConfig.getCookieMaxAge() == null) {
+            for (WebXml fragment : fragments) {
+                Integer value = fragment.getSessionConfig().getCookieMaxAge();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieMaxAge() == null) {
+                        temp.getSessionConfig().setCookieMaxAge(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieMaxAge())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieMaxAge",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieMaxAge() != null) {
+                sessionConfig.setCookieMaxAge(
+                        temp.getSessionConfig().getCookieMaxAge().toString());
+            }
+        }
+
+        if (sessionConfig.getSessionTrackingModes().size() == 0) {
+            for (WebXml fragment : fragments) {
+                EnumSet<SessionTrackingMode> value =
+                    fragment.getSessionConfig().getSessionTrackingModes();
+                if (value.size() > 0) {
+                    if (temp.getSessionConfig().getSessionTrackingModes().size() == 0) {
+                        temp.getSessionConfig().getSessionTrackingModes().addAll(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getSessionTrackingModes())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionTrackingMode",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.getSessionTrackingModes().addAll(
+                    temp.getSessionConfig().getSessionTrackingModes());
+        }
+        
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getTaglibs(), taglibs,
+                    temp.getTaglibs(), fragment, "Taglibs")) {
+                return false;
+            }
+        }
+        taglibs.putAll(temp.getTaglibs());
+
+        for (WebXml fragment : fragments) {
+            for (String welcomeFile : fragment.getWelcomeFiles()) {
+                // Always additive
+                addWelcomeFile(welcomeFile);
+            }
+        }
+
+        return true;
+    }
+    
+    private static <T extends ResourceBase> boolean mergeResourceMap(
+            Map<String, T> fragmentResources, Map<String, T> mainResources,
+            Map<String, T> tempResources,
+            Map<String,Boolean> mergeInjectionFlags, WebXml fragment) {
+        for (T resource : fragmentResources.values()) {
+            String resourceName = resource.getName();
+            boolean mergeInjectionFlag = false;
+            if (mainResources.containsKey(resourceName)) {
+                if (mergeInjectionFlags.containsKey(resourceName)) {
+                    mergeInjectionFlag =
+                        mergeInjectionFlags.get(resourceName).booleanValue(); 
+                } else {
+                    if (mainResources.get(
+                            resourceName).getInjectionTargets().size() == 0) {
+                        mergeInjectionFlag = true;
+                    }
+                    mergeInjectionFlags.put(resourceName,
+                            Boolean.valueOf(mergeInjectionFlag));
+                }
+                if (mergeInjectionFlag) {
+                    mainResources.get(resourceName).getInjectionTargets().addAll(
+                            resource.getInjectionTargets());
+                }
+            } else {
+                // Not defined in main web.xml
+                if (tempResources.containsKey(resourceName)) {
+                    log.error(sm.getString(
+                            "webXml.mergeConflictResource",
+                            resourceName,
+                            fragment.getName(),
+                            fragment.getURL()));
+                    return false;
+                } 
+                tempResources.put(resourceName, resource);
+            }
+        }
+        return true;
+    }
+    
+    private static <T> boolean mergeMap(Map<String,T> fragmentMap,
+            Map<String,T> mainMap, Map<String,T> tempMap, WebXml fragment,
+            String mapName) {
+        for (String key : fragmentMap.keySet()) {
+            if (!mainMap.containsKey(key)) {
+                // Not defined in main web.xml
+                T value = fragmentMap.get(key);
+                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;
+    }
+    
+    private static boolean mergeFilter(FilterDef src, FilterDef dest,
+            boolean failOnConflict) {
+        if (dest.getAsyncSupported() == null) {
+            dest.setAsyncSupported(src.getAsyncSupported());
+        } else if (src.getAsyncSupported() != null) {
+            if (failOnConflict &&
+                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
+                return false;
+            }
+        }
+
+        if (dest.getFilterClass()  == null) {
+            dest.setFilterClass(src.getFilterClass());
+        } else if (src.getFilterClass() != null) {
+            if (failOnConflict &&
+                    !src.getFilterClass().equals(dest.getFilterClass())) {
+                return false;
+            }
+        }
+        
+        for (Map.Entry<String,String> srcEntry :
+                src.getParameterMap().entrySet()) {
+            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
+                if (failOnConflict && !dest.getParameterMap().get(
+                        srcEntry.getKey()).equals(srcEntry.getValue())) {
+                    return false;
+                }
+            } else {
+                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
+            }
+        }
+        return true;
+    }
+    
+    private static boolean mergeServlet(ServletDef src, ServletDef dest,
+            boolean failOnConflict) {
+        // These tests should be unnecessary...
+        if (dest.getServletClass() != null && dest.getJspFile() != null) {
+            return false;
+        }
+        if (src.getServletClass() != null && src.getJspFile() != null) {
+            return false;
+        }
+        
+        
+        if (dest.getServletClass() == null && dest.getJspFile() == null) {
+            dest.setServletClass(src.getServletClass());
+            dest.setJspFile(src.getJspFile());
+        } else if (failOnConflict) {
+            if (src.getServletClass() != null &&
+                    (dest.getJspFile() != null ||
+                            !src.getServletClass().equals(dest.getServletClass()))) {
+                return false;
+            }
+            if (src.getJspFile() != null &&
+                    (dest.getServletClass() != null ||
+                            !src.getJspFile().equals(dest.getJspFile()))) {
+                return false;
+            }
+        }
+        
+        // Additive
+        for (SecurityRoleRef securityRoleRef : src.getSecurityRoleRefs()) {
+            dest.addSecurityRoleRef(securityRoleRef);
+        }
+        
+        if (dest.getLoadOnStartup() == null) {
+            dest.setLoadOnStartup(src.getLoadOnStartup().toString());
+        } else if (src.getLoadOnStartup() != null) {
+            if (failOnConflict &&
+                    !src.getLoadOnStartup().equals(dest.getLoadOnStartup())) {
+                return false;
+            }
+        }
+        
+        if (dest.getEnabled() == null) {
+            dest.setEnabled(src.getEnabled().toString());
+        } else if (src.getEnabled() != null) {
+            if (failOnConflict &&
+                    !src.getEnabled().equals(dest.getEnabled())) {
+                return false;
+            }
+        }
+        
+        for (Map.Entry<String,String> srcEntry :
+                src.getParameterMap().entrySet()) {
+            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
+                if (failOnConflict && !dest.getParameterMap().get(
+                        srcEntry.getKey()).equals(srcEntry.getValue())) {
+                    return false;
+                }
+            } else {
+                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
+            }
+        }
+        
+        if (dest.getMultipartDef() == null) {
+            dest.setMultipartDef(src.getMultipartDef());
+        } else if (src.getMultipartDef() != null) {
+            return mergeMultipartDef(src.getMultipartDef(),
+                    dest.getMultipartDef(), failOnConflict);
+        }
+        
+        if (dest.getAsyncSupported() == null) {
+            dest.setAsyncSupported(src.getAsyncSupported().toString());
+        } else if (src.getAsyncSupported() != null) {
+            if (failOnConflict &&
+                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+
+    private static boolean mergeMultipartDef(MultipartDef src, MultipartDef dest,
+            boolean failOnConflict) {
+
+        if (dest.getLocation() == null) {
+            dest.setLocation(src.getLocation());
+        } else if (src.getLocation() != null) {
+            if (failOnConflict &&
+                    !src.getLocation().equals(dest.getLocation())) {
+                return false;
+            }
+        }
+
+        if (dest.getFileSizeThreshold() == null) {
+            dest.setFileSizeThreshold(src.getFileSizeThreshold());
+        } else if (src.getFileSizeThreshold() != null) {
+            if (failOnConflict &&
+                    !src.getFileSizeThreshold().equals(
+                            dest.getFileSizeThreshold())) {
+                return false;
+            }
+        }
+
+        if (dest.getMaxFileSize() == null) {
+            dest.setMaxFileSize(src.getMaxFileSize());
+        } else if (src.getLocation() != null) {
+            if (failOnConflict &&
+                    !src.getMaxFileSize().equals(dest.getMaxFileSize())) {
+                return false;
+            }
+        }
+
+        if (dest.getMaxRequestSize() == null) {
+            dest.setMaxRequestSize(src.getMaxRequestSize());
+        } else if (src.getMaxRequestSize() != null) {
+            if (failOnConflict &&
+                    !src.getMaxRequestSize().equals(
+                            dest.getMaxRequestSize())) {
+                return false;
+            }
+        }
+
+        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
+     * Servlet spec.
+     * 
+     * @param application   The application web.xml file
+     * @param fragments     The map of fragment names to web fragments
+     * @return Ordered list of web-fragment.xml files to process
+     */
+    public static Set<WebXml> orderWebFragments(WebXml application,
+            Map<String,WebXml> fragments) {
+
+        Set<WebXml> orderedFragments = new LinkedHashSet<WebXml>();
+        
+        boolean absoluteOrdering =
+            (application.getAbsoluteOrdering() != null);
+        
+        if (absoluteOrdering) {
+            // Only those fragments listed should be processed
+            Set<String> requestedOrder = application.getAbsoluteOrdering();
+            
+            for (String requestedName : requestedOrder) {
+                if (WebXml.ORDER_OTHERS.equals(requestedName)) {
+                    // Add all fragments not named explicitly at this point
+                    for (String name : fragments.keySet()) {
+                        if (!requestedOrder.contains(name)) {
+                            WebXml fragment = fragments.get(name);
+                            if (fragment != null) {
+                                orderedFragments.add(fragment);
+                            }
+                        }
+                    }
+                } else {
+                    WebXml fragment = fragments.get(requestedName);
+                    if (fragment != null) {
+                        orderedFragments.add(fragment);
+                    }
+                }
+            }
+        } else {
+            List<String> order = new LinkedList<String>();
+            // Start by adding all fragments - order doesn't matter
+            order.addAll(fragments.keySet());
+            
+            // Now go through and move elements to start/end depending on if
+            // they specify others
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                if (fragment.getBeforeOrder().contains(WebXml.ORDER_OTHERS)) {
+                    // Move to beginning
+                    order.remove(name);
+                    order.add(0, name);
+                } else if (fragment.getAfterOrder().contains(WebXml.ORDER_OTHERS)) {
+                    // Move to end
+                    order.remove(name);
+                    order.add(name);
+                }
+            }
+            
+            // Now apply remaining ordering
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                for (String before : fragment.getBeforeOrder()) {
+                    if (!before.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(before) &&
+                            order.indexOf(before) < order.indexOf(name)) {
+                        order.remove(name);
+                        order.add(order.indexOf(before), name);
+                    }
+                }
+                for (String after : fragment.getAfterOrder()) {
+                    if (!after.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(after) &&
+                            order.indexOf(after) > order.indexOf(name)) {
+                        order.remove(name);
+                        order.add(order.indexOf(after) + 1, name);
+                    }
+                }
+            }
+            
+            // Finally check ordering was applied correctly - if there are
+            // errors then that indicates circular references
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                for (String before : fragment.getBeforeOrder()) {
+                    if (!before.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(before) &&
+                            order.indexOf(before) < order.indexOf(name)) {
+                        throw new IllegalArgumentException(sm.getString(""));
+                    }
+                }
+                for (String after : fragment.getAfterOrder()) {
+                    if (!after.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(after) &&
+                            order.indexOf(after) > order.indexOf(name)) {
+                        throw new IllegalArgumentException();
+                    }
+                }
+            }
+            
+            // Build the ordered list
+            for (String name : order) {
+                orderedFragments.add(fragments.get(name));
+            }
+        }
+        
+        return orderedFragments;
+    }
+
+}    
index bd3a5d9..f2d6890 100644 (file)
@@ -70,6 +70,7 @@ import org.apache.catalina.deploy.FilterMap;
 import org.apache.catalina.deploy.LoginConfig;
 import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.deploy.ServletDef;
+import org.apache.catalina.deploy.WebXml;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.naming.resources.DirContextURLConnection;
index 07011b1..c5db475 100644 (file)
@@ -116,29 +116,4 @@ userConfig.start=UserConfig: Processing START
 userConfig.stop=UserConfig: Processing STOP
 webRuleSet.absoluteOrdering=<absolute-ordering> element not valid in web-fragment.xml and will be ignored
 webRuleSet.relativeOrdering=<ordering> element not valid in web.xml and will be ignored
-webXml.duplicateEnvEntry=Duplicate env-entry name [{0}]
-webXml.duplicateFilter=Duplicate filter name [{0}]
-webXml.duplicateMessageDestination=Duplicate message-destination name [{0}]
-webXml.duplicateMessageDestinationRef=Duplicate message-destination-ref name [{0}]
-webXml.duplicateResourceEnvRef=Duplicate resource-env-ref name [{0}]
-webXml.duplicateResourceRef=Duplicate resource-ref name [{0}]
-webXml.duplicateTaglibUri=Duplicate tag library URI [{0}]
-webXml.reservedName=A web.xml file was detected using a reserved name [{0}]. The name element will be ignored for this fragment.
-webXml.mergeConflictDisplayName=The display name was defined in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictErrorPage=The Error Page for [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictFilter=The Filter [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictLoginConfig=A LoginConfig was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictOrder=Fragment relative ordering contains circular references. Thsi can be resolved by using absolute ordering in web.xml.
-webXml.mergeConflictResource=The Resource [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictServlet=The Servlet [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
-webXml.mergeConflictSessionCookieName=The session cookie name was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieDomain=The session cookie domain was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookiePath=The session cookie path was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieComment=The session cookie comment was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieHttpOnly=The session cookie http-only flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieSecure=The session cookie secure flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionCookieMaxAge=The session cookie max-age was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionTimeout=The session timeout was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictSessionTrackingMode=The session tracking modes were defined inconsistently in multiple fragments including fragment with name [{0}] located at [{1}]
-webXml.mergeConflictString=The [{0}] with name [{1}] was defined inconsistently in multiple fragments including fragment with name [{2}] located at [{3}]
-webXml.multipleOther=Multiple others entries in ordering
+
index dea67e4..4e2468c 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.catalina.deploy.ContextHandler;
 import org.apache.catalina.deploy.ContextService;
 import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.deploy.ServletDef;
+import org.apache.catalina.deploy.WebXml;
 import org.apache.tomcat.util.IntrospectionUtils;
 import org.apache.tomcat.util.digester.CallMethodRule;
 import org.apache.tomcat.util.digester.CallParamRule;
diff --git a/java/org/apache/catalina/startup/WebXml.java b/java/org/apache/catalina/startup/WebXml.java
deleted file mode 100644 (file)
index 2ce6f2a..0000000
+++ /dev/null
@@ -1,2052 +0,0 @@
-/*
- * 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.net.URL;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.MultipartConfigElement;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-
-import org.apache.catalina.Context;
-import org.apache.catalina.Wrapper;
-import org.apache.catalina.deploy.ContextEjb;
-import org.apache.catalina.deploy.ContextEnvironment;
-import org.apache.catalina.deploy.ContextHandler;
-import org.apache.catalina.deploy.ContextLocalEjb;
-import org.apache.catalina.deploy.ContextResource;
-import org.apache.catalina.deploy.ContextResourceEnvRef;
-import org.apache.catalina.deploy.ContextService;
-import org.apache.catalina.deploy.ErrorPage;
-import org.apache.catalina.deploy.FilterDef;
-import org.apache.catalina.deploy.FilterMap;
-import org.apache.catalina.deploy.InjectionTarget;
-import org.apache.catalina.deploy.JspPropertyGroup;
-import org.apache.catalina.deploy.LoginConfig;
-import org.apache.catalina.deploy.MessageDestination;
-import org.apache.catalina.deploy.MessageDestinationRef;
-import org.apache.catalina.deploy.MultipartDef;
-import org.apache.catalina.deploy.ResourceBase;
-import org.apache.catalina.deploy.SecurityCollection;
-import org.apache.catalina.deploy.SecurityConstraint;
-import org.apache.catalina.deploy.SecurityRoleRef;
-import org.apache.catalina.deploy.ServletDef;
-import org.apache.catalina.deploy.SessionConfig;
-import org.apache.tomcat.util.res.StringManager;
-
-/**
- * Representation of common elements of web.xml and web-fragment.xml. Provides
- * a repository for parsed data before the elements are merged.
- * Validation is spread between multiple classes:
- * The digester checks for structural correctness (eg single login-config)
- * This class checks for invalid duplicates (eg filter/servlet names)
- * StandardContext will check validity of values (eg URL formats etc)
- */
-public class WebXml {
-    
-    protected static final String ORDER_OTHERS =
-        "org.apache.catalina.order.others";
-    
-    private static final StringManager sm =
-        StringManager.getManager(Constants.Package);
-
-    private static final org.apache.juli.logging.Log log=
-        org.apache.juli.logging.LogFactory.getLog(WebXml.class);
-    
-    // web.xml only elements
-    // Absolute Ordering
-    private Set<String> absoluteOrdering = null;
-    public void addAbsoluteOrdering(String fragmentName) {
-        if (absoluteOrdering == null) {
-            absoluteOrdering = new LinkedHashSet<String>();
-        }
-        absoluteOrdering.add(fragmentName);
-    }
-    public void addAbsoluteOrderingOthers() {
-        if (absoluteOrdering == null) {
-            absoluteOrdering = new LinkedHashSet<String>();
-        }
-        absoluteOrdering.add(ORDER_OTHERS);
-    }
-    public Set<String> getAbsoluteOrdering() {
-        return absoluteOrdering;
-    }
-
-    // web-fragment.xml only elements
-    // Relative ordering
-    private Set<String> after = new LinkedHashSet<String>();
-    public void addAfterOrder(String fragmentName) {
-        after.add(fragmentName);
-    }
-    public void addAfterOrderOthers() {
-        if (before.contains(ORDER_OTHERS)) {
-            throw new IllegalArgumentException(sm.getString(
-                    "webXml.multipleOther"));
-        }
-        after.add(ORDER_OTHERS);
-    }
-    public Set<String> getAfterOrder() { return after; }
-    
-    private Set<String> before = new LinkedHashSet<String>();
-    public void addBeforeOrder(String fragmentName) {
-        before.add(fragmentName);
-    }
-    public void addBeforeOrderOthers() {
-        if (after.contains(ORDER_OTHERS)) {
-            throw new IllegalArgumentException(sm.getString(
-                    "webXml.multipleOther"));
-        }
-        before.add(ORDER_OTHERS);
-    }
-    public Set<String> getBeforeOrder() { return before; }
-
-    // Common elements and attributes
-    
-    // Required attribute of web-app element
-    private String version = null;
-    public String getVersion() { return version; }
-    public void setVersion(String version) { this.version = version; }
-
-    // Optional publicId attribute
-    private String publicId = null;
-    public String getPublicId() { return publicId; }
-    public void setPublicId(String publicId) { this.publicId = publicId; }
-    
-    // Optional metadata-complete attribute
-    private boolean metadataComplete = false;
-    public boolean isMetadataComplete() { return metadataComplete; }
-    public void setMetadataComplete(boolean metadataComplete) {
-        this.metadataComplete = metadataComplete; }
-    
-    // Optional name element
-    private String name = null;
-    public String getName() { return name; }
-    public void setName(String name) {
-        if (ORDER_OTHERS.equalsIgnoreCase(name)) {
-            // This is unusual. This name will be ignored. Log the fact.
-            log.warn(sm.getString("webXml.reservedName", name));
-        } else {
-            this.name = name;
-        }
-    }
-
-    // web-app elements
-    // TODO: Ignored elements:
-    // - description
-    // - icon
-
-    // display-name - TODO should support multiple with language
-    private String displayName = null;
-    public String getDisplayName() { return displayName; }
-    public void setDisplayName(String displayName) {
-        this.displayName = displayName;
-    }
-    
-    // distributable
-    private boolean distributable = false;
-    public boolean isDistributable() { return distributable; }
-    public void setDistributable(boolean distributable) {
-        this.distributable = distributable;
-    }
-    
-    // context-param
-    // TODO: description (multiple with language) is ignored
-    private Map<String,String> contextParams = new HashMap<String,String>();
-    public void addContextParam(String param, String value) {
-        contextParams.put(param, value);
-    }
-    public Map<String,String> getContextParams() { return contextParams; }
-    
-    // filter
-    // TODO: Should support multiple description elements with language
-    // TODO: Should support multiple display-name elements with language
-    // TODO: Should support multiple icon elements
-    // TODO: Description for init-param is ignored
-    private Map<String,FilterDef> filters =
-        new LinkedHashMap<String,FilterDef>();
-    public void addFilter(FilterDef filter) {
-        if (filters.containsKey(filter.getFilterName())) {
-            // Filter names must be unique within a web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateFilter",
-                            filter.getFilterName()));
-        }
-        filters.put(filter.getFilterName(), filter);
-    }
-    public Map<String,FilterDef> getFilters() { return filters; }
-    
-    // filter-mapping
-    private Set<FilterMap> filterMaps = new LinkedHashSet<FilterMap>();
-    public void addFilterMapping(FilterMap filterMap) {
-        filterMaps.add(filterMap);
-    }
-    public Set<FilterMap> getFilterMappings() { return filterMaps; }
-    
-    // listener
-    // TODO: description (multiple with language) is ignored
-    // TODO: display-name (multiple with language) is ignored
-    // TODO: icon (multiple) is ignored
-    private Set<String> listeners = new LinkedHashSet<String>();
-    public void addListener(String className) {
-        listeners.add(className);
-    }
-    public Set<String> getListeners() { return listeners; }
-    
-    // servlet
-    // TODO: description (multiple with language) is ignored
-    // TODO: display-name (multiple with language) is ignored
-    // TODO: icon (multiple) is ignored
-    // TODO: init-param/description (multiple with language) is ignored
-    // TODO: security-role-ref/description (multiple with language) is ignored
-    private Map<String,ServletDef> servlets = new HashMap<String,ServletDef>();
-    public void addServlet(ServletDef servletDef) {
-        servlets.put(servletDef.getServletName(), servletDef);
-    }
-    public Map<String,ServletDef> getServlets() { return servlets; }
-    
-    // servlet-mapping
-    private Map<String,String> servletMappings = new HashMap<String,String>();
-    public void addServletMapping(String urlPattern, String servletName) {
-        servletMappings.put(urlPattern, servletName);
-    }
-    public Map<String,String> getServletMappings() { return servletMappings; }
-    
-    // session-config
-    // Digester will check there is only one of these
-    private SessionConfig sessionConfig = new SessionConfig();
-    public void setSessionConfig(SessionConfig sessionConfig) {
-        this.sessionConfig = sessionConfig;
-    }
-    public SessionConfig getSessionConfig() { return sessionConfig; }
-    
-    // mime-mapping
-    private Map<String,String> mimeMappings = new HashMap<String,String>();
-    public void addMimeMapping(String extension, String mimeType) {
-        mimeMappings.put(extension, mimeType);
-    }
-    public Map<String,String> getMimeMappings() { return mimeMappings; }
-    
-    // welcome-file-list
-    // When merging web.xml files it may be necessary for any new welcome files
-    // to completely replace the current set
-    private boolean replaceWelcomeFiles = false;
-    public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) {
-        this.replaceWelcomeFiles = replaceWelcomeFiles;
-    }
-    private Set<String> welcomeFiles = new LinkedHashSet<String>();
-    public void addWelcomeFile(String welcomeFile) {
-        if (replaceWelcomeFiles) {
-            welcomeFiles.clear();
-            replaceWelcomeFiles = false;
-        }
-        welcomeFiles.add(welcomeFile);
-    }
-    public Set<String> getWelcomeFiles() { return welcomeFiles; }
-    
-    // error-page
-    private Map<String,ErrorPage> errorPages = new HashMap<String,ErrorPage>();
-    public void addErrorPage(ErrorPage errorPage) {
-        errorPages.put(errorPage.getName(), errorPage);
-    }
-    public Map<String,ErrorPage> getErrorPages() { return errorPages; }
-    
-    // Digester will check there is only one jsp-config
-    // jsp-config/taglib or taglib (2.3 and earlier)
-    private Map<String,String> taglibs = new HashMap<String,String>();
-    public void addTaglib(String uri, String location) {
-        if (taglibs.containsKey(uri)) {
-            // Taglib URIs must be unique within a web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateTaglibUri", uri));
-        }
-        taglibs.put(uri, location);
-    }
-    public Map<String,String> getTaglibs() { return taglibs; }
-    
-    // jsp-config/jsp-property-group
-    private Set<JspPropertyGroup> jspPropertyGroups =
-        new HashSet<JspPropertyGroup>();
-    public void addJspPropertyGroup(JspPropertyGroup propertyGroup) {
-        jspPropertyGroups.add(propertyGroup);
-    }
-    public Set<JspPropertyGroup> getJspPropertyGroups() {
-        return jspPropertyGroups;
-    }
-
-    // security-constraint
-    // TODO: Should support multiple display-name elements with language
-    // TODO: Should support multiple description elements with language
-    private Set<SecurityConstraint> securityConstraints =
-        new HashSet<SecurityConstraint>();
-    public void addSecurityConstraint(SecurityConstraint securityConstraint) {
-        securityConstraints.add(securityConstraint);
-    }
-    public Set<SecurityConstraint> getSecurityConstraints() {
-        return securityConstraints;
-    }
-    
-    // login-config
-    // Digester will check there is only one of these
-    private LoginConfig loginConfig = null;
-    public void setLoginConfig(LoginConfig loginConfig) {
-        this.loginConfig = loginConfig;
-    }
-    public LoginConfig getLoginConfig() { return loginConfig; }
-    
-    // security-role
-    // TODO: description (multiple with language) is ignored
-    private Set<String> securityRoles = new HashSet<String>();
-    public void addSecurityRole(String securityRole) {
-        securityRoles.add(securityRole);
-    }
-    public Set<String> getSecurityRoles() { return securityRoles; }
-    
-    // env-entry
-    // TODO: Should support multiple description elements with language
-    private Map<String,ContextEnvironment> envEntries =
-        new HashMap<String,ContextEnvironment>();
-    public void addEnvEntry(ContextEnvironment envEntry) {
-        if (envEntries.containsKey(envEntry.getName())) {
-            // env-entry names must be unique within a web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateEnvEntry",
-                            envEntry.getName()));
-        }
-        envEntries.put(envEntry.getName(),envEntry);
-    }
-    public Map<String,ContextEnvironment> getEnvEntries() { return envEntries; }
-    
-    // ejb-ref
-    // TODO: Should support multiple description elements with language
-    private Map<String,ContextEjb> ejbRefs = new HashMap<String,ContextEjb>();
-    public void addEjbRef(ContextEjb ejbRef) {
-        ejbRefs.put(ejbRef.getName(),ejbRef);
-    }
-    public Map<String,ContextEjb> getEjbRefs() { return ejbRefs; }
-    
-    // ejb-local-ref
-    // TODO: Should support multiple description elements with language
-    private Map<String,ContextLocalEjb> ejbLocalRefs =
-        new HashMap<String,ContextLocalEjb>();
-    public void addEjbLocalRef(ContextLocalEjb ejbLocalRef) {
-        ejbLocalRefs.put(ejbLocalRef.getName(),ejbLocalRef);
-    }
-    public Map<String,ContextLocalEjb> getEjbLocalRefs() {
-        return ejbLocalRefs;
-    }
-    
-    // service-ref
-    // TODO: Should support multiple description elements with language
-    // TODO: Should support multiple display-names elements with language
-    // TODO: Should support multiple icon elements ???
-    private Map<String,ContextService> serviceRefs =
-        new HashMap<String,ContextService>();
-    public void addServiceRef(ContextService serviceRef) {
-        serviceRefs.put(serviceRef.getName(), serviceRef);
-    }
-    public Map<String,ContextService> getServiceRefs() { return serviceRefs; }
-    
-    // resource-ref
-    // TODO: Should support multiple description elements with language
-    private Map<String,ContextResource> resourceRefs =
-        new HashMap<String,ContextResource>();
-    public void addResourceRef(ContextResource resourceRef) {
-        if (resourceRefs.containsKey(resourceRef.getName())) {
-            // resource-ref names must be unique within a web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateResourceRef",
-                            resourceRef.getName()));
-        }
-        resourceRefs.put(resourceRef.getName(), resourceRef);
-    }
-    public Map<String,ContextResource> getResourceRefs() {
-        return resourceRefs;
-    }
-    
-    // resource-env-ref
-    // TODO: Should support multiple description elements with language
-    private Map<String,ContextResourceEnvRef> resourceEnvRefs =
-        new HashMap<String,ContextResourceEnvRef>();
-    public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
-        if (resourceEnvRefs.containsKey(resourceEnvRef.getName())) {
-            // resource-env-ref names must be unique within a web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateResourceEnvRef",
-                            resourceEnvRef.getName()));
-        }
-        resourceEnvRefs.put(resourceEnvRef.getName(), resourceEnvRef);
-    }
-    public Map<String,ContextResourceEnvRef> getResourceEnvRefs() {
-        return resourceEnvRefs;
-    }
-    
-    // message-destination-ref
-    // TODO: Should support multiple description elements with language
-    private Map<String,MessageDestinationRef> messageDestinationRefs =
-        new HashMap<String,MessageDestinationRef>();
-    public void addMessageDestinationRef(
-            MessageDestinationRef messageDestinationRef) {
-        if (messageDestinationRefs.containsKey(
-                messageDestinationRef.getName())) {
-            // message-destination-ref names must be unique within a
-            // web(-fragment).xml
-            throw new IllegalArgumentException(sm.getString(
-                    "webXml.duplicateMessageDestinationRef",
-                    messageDestinationRef.getName()));
-        }
-        messageDestinationRefs.put(messageDestinationRef.getName(),
-                messageDestinationRef);
-    }
-    public Map<String,MessageDestinationRef> getMessageDestinationRefs() {
-        return messageDestinationRefs;
-    }
-    
-    // message-destination
-    // TODO: Should support multiple description elements with language
-    // TODO: Should support multiple display-names elements with language
-    // TODO: Should support multiple icon elements ???
-    private Map<String,MessageDestination> messageDestinations =
-        new HashMap<String,MessageDestination>();
-    public void addMessageDestination(
-            MessageDestination messageDestination) {
-        if (messageDestinations.containsKey(
-                messageDestination.getName())) {
-            // message-destination names must be unique within a
-            // web(-fragment).xml
-            throw new IllegalArgumentException(
-                    sm.getString("webXml.duplicateMessageDestination",
-                            messageDestination.getName()));
-        }
-        messageDestinations.put(messageDestination.getName(),
-                messageDestination);
-    }
-    public Map<String,MessageDestination> getMessageDestinations() {
-        return messageDestinations;
-    }
-    
-    // locale-encoging-mapping-list
-    private Map<String,String> localeEncodingMappings =
-        new HashMap<String,String>();
-    public void addLocaleEncodingMapping(String locale, String encoding) {
-        localeEncodingMappings.put(locale, encoding);
-    }
-    public Map<String,String> getLocalEncodingMappings() {
-        return localeEncodingMappings;
-    }
-    
-
-    // Attributes not defined in web.xml or web-fragment.xml
-    
-    // URL of JAR / exploded JAR for this web-fragment
-    private URL uRL = null;
-    public void setURL(URL url) { this.uRL = url; }
-    public URL getURL() { return uRL; }
-    
-    
-    
-    @Override
-    public String toString() {
-        StringBuilder buf = new StringBuilder(32);
-        buf.append("Name: ");
-        buf.append(getName());
-        buf.append(", URL: ");
-        buf.append(getURL());
-        return buf.toString();
-    }
-    
-    private static final String INDENT2 = "  ";
-    private static final String INDENT4 = "    ";
-    private static final String INDENT6 = "      ";
-    
-    /**
-     * Generate a web.xml in String form that matches the representation stored
-     * in this object.
-     * 
-     * @return The complete contents of web.xml as a String
-     */
-    public String toXml() {
-        StringBuilder sb = new StringBuilder(2048);
-        
-        // TODO - Various, icon, description etc elements are skipped - mainly
-        //        because they are ignored when web.xml is parsed - see above
-
-        // Declaration
-        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-        
-        // Root element
-        sb.append("<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n");
-        sb.append("         xmlns:xsi=");
-        sb.append("\"http://www.w3.org/2001/XMLSchema-instance\"\n");
-        sb.append("         xsi:schemaLocation=");
-        sb.append("\"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"\n");
-        sb.append("         version=\"");
-        if (version != null) {
-            sb.append(version);
-        } else {
-            // Should be non-null but in case it isn't assume 3.0
-            sb.append("3.0");
-        }
-        sb.append("\"\n");
-        sb.append("         metadata-complete=\"true\">\n\n");
-
-        appendElement(sb, INDENT2, "display-name", displayName);
-        
-        if (isDistributable()) {
-            sb.append("  <distributable/>\n\n");
-        }
-        
-        for (Map.Entry<String, String> entry : contextParams.entrySet()) {
-            sb.append("  <context-param>\n");
-            appendElement(sb, INDENT4, "param-name", entry.getKey());
-            appendElement(sb, INDENT4, "param-valuee", entry.getValue());
-            sb.append("  </context-param>\n");
-        }
-        sb.append('\n');
-        
-        for (Map.Entry<String, FilterDef> entry : filters.entrySet()) {
-            FilterDef filterDef = entry.getValue();
-            sb.append("  <filter>\n");
-            appendElement(sb, INDENT4, "description",
-                    filterDef.getDescription());
-            appendElement(sb, INDENT4, "display-name",
-                    filterDef.getDisplayName());
-            appendElement(sb, INDENT4, "filter-name",
-                    filterDef.getFilterName());
-            appendElement(sb, INDENT4, "filter-class",
-                    filterDef.getFilterClass());
-            appendElement(sb, INDENT4, "async-supported",
-                    filterDef.getAsyncSupported());
-            for (Map.Entry<String, String> param :
-                    filterDef.getParameterMap().entrySet()) {
-                sb.append("    <init-param>\n");
-                appendElement(sb, INDENT6, "param-name", param.getKey());
-                appendElement(sb, INDENT6, "param-value", param.getValue());
-                sb.append("    </init-param>\n");
-            }
-            sb.append("  </filter>\n");
-        }
-        sb.append('\n');
-
-        for (FilterMap filterMap : filterMaps) {
-            sb.append("  <filter-mapping>\n");
-            appendElement(sb, INDENT4, "filter-name",
-                    filterMap.getFilterName());
-            if (filterMap.getMatchAllServletNames()) {
-                sb.append("    <servlet-name>*</servlet-name>\n");
-            } else {
-                for (String servletName : filterMap.getServletNames()) {
-                    appendElement(sb, INDENT4, "servlet-name", servletName);
-                }
-            }
-            if (filterMap.getMatchAllUrlPatterns()) {
-                sb.append("    <url-pattern>*</url-pattern>\n");
-            } else {
-                for (String urlPattern : filterMap.getURLPatterns()) {
-                    appendElement(sb, INDENT4, "url-pattern", urlPattern);
-                }
-            }
-            for (String dispatcher : filterMap.getDispatcherNames()) {
-                appendElement(sb, INDENT4, "dispatcher", dispatcher);
-            }
-            sb.append("  </filter-mapping>\n");
-        }
-        sb.append('\n');
-
-        for (String listener : listeners) {
-            sb.append("  <listener>\n");
-            appendElement(sb, INDENT4, "listener-class", listener);
-            sb.append("  </listener>\n");
-        }
-        sb.append('\n');
-
-        for (Map.Entry<String, ServletDef> entry : servlets.entrySet()) {
-            ServletDef servletDef = entry.getValue();
-            sb.append("  <servlet>\n");
-            appendElement(sb, INDENT4, "description",
-                    servletDef.getDescription());
-            appendElement(sb, INDENT4, "display-name",
-                    servletDef.getDisplayName());
-            appendElement(sb, INDENT4, "servlet-name", entry.getKey());
-            appendElement(sb, INDENT4, "servlet-class",
-                    servletDef.getServletClass());
-            appendElement(sb, INDENT4, "jsp-file", servletDef.getJspFile());
-            for (Map.Entry<String, String> param :
-                    servletDef.getParameterMap().entrySet()) {
-                sb.append("    <init-param>\n");
-                appendElement(sb, INDENT6, "param-name", param.getKey());
-                appendElement(sb, INDENT6, "param-value", param.getValue());
-                sb.append("    </init-param>\n");
-            }
-            appendElement(sb, INDENT4, "load-on-startup",
-                    servletDef.getLoadOnStartup());
-            appendElement(sb, INDENT4, "enabled", servletDef.getEnabled());
-            appendElement(sb, INDENT4, "async-supported",
-                    servletDef.getAsyncSupported());
-            if (servletDef.getRunAs() != null) {
-                sb.append("    <run-as>\n");
-                appendElement(sb, INDENT6, "role-name", servletDef.getRunAs());
-                sb.append("    </run-as>\n");
-            }
-            for (SecurityRoleRef roleRef : servletDef.getSecurityRoleRefs()) {
-                sb.append("    <security-role-ref>\n");
-                appendElement(sb, INDENT6, "role-name", roleRef.getName());
-                appendElement(sb, INDENT6, "role-link", roleRef.getLink());
-                sb.append("    </security-role-ref>\n");
-            }
-            MultipartDef multipartDef = servletDef.getMultipartDef();
-            if (multipartDef != null) {
-                sb.append("    <multipart-config>\n");
-                appendElement(sb, INDENT6, "location",
-                        multipartDef.getLocation());
-                appendElement(sb, INDENT6, "max-file-size",
-                        multipartDef.getMaxFileSize());
-                appendElement(sb, INDENT6, "max-request-size",
-                        multipartDef.getMaxRequestSize());
-                appendElement(sb, INDENT6, "file-size-threshold",
-                        multipartDef.getFileSizeThreshold());
-                sb.append("    </multipart-config>\n");
-            }
-            sb.append("  </servlet>\n");
-        }
-        sb.append('\n');
-
-        for (Map.Entry<String, String> entry : servletMappings.entrySet()) {
-            sb.append("  <servlet-mapping>\n");
-            appendElement(sb, INDENT4, "servlet-name", entry.getValue());
-            appendElement(sb, INDENT4, "url-pattern", entry.getKey());
-            sb.append("  </servlet-mapping>\n");
-        }
-        sb.append('\n');
-        
-        if (sessionConfig != null) {
-            sb.append("  <session-config>\n");
-            appendElement(sb, INDENT4, "session-timeout",
-                    sessionConfig.getSessionTimeout());
-            sb.append("    <cookie-config>\n");
-            appendElement(sb, INDENT6, "name", sessionConfig.getCookieName());
-            appendElement(sb, INDENT6, "domain",
-                    sessionConfig.getCookieDomain());
-            appendElement(sb, INDENT6, "path", sessionConfig.getCookiePath());
-            appendElement(sb, INDENT6, "comment",
-                    sessionConfig.getCookieComment());
-            appendElement(sb, INDENT6, "http-only",
-                    sessionConfig.getCookieHttpOnly());
-            appendElement(sb, INDENT6, "secure",
-                    sessionConfig.getCookieSecure());
-            appendElement(sb, INDENT6, "max-age",
-                    sessionConfig.getCookieMaxAge());
-            sb.append("    </cookie-config>\n");
-            for (SessionTrackingMode stm :
-                    sessionConfig.getSessionTrackingModes()) {
-                appendElement(sb, INDENT4, "tracking-mode", stm.name());
-            }
-            sb.append("  </session-config>\n\n");
-        }
-        
-        for (Map.Entry<String, String> entry : mimeMappings.entrySet()) {
-            sb.append("  <mime-mapping>\n");
-            appendElement(sb, INDENT4, "extension", entry.getKey());
-            appendElement(sb, INDENT4, "mime-type", entry.getValue());
-            sb.append("  </mime-mapping>\n");
-        }
-        sb.append('\n');
-        
-        if (welcomeFiles.size() > 0) {
-            sb.append("  <welcome-file-list>\n");
-            for (String welcomeFile : welcomeFiles) {
-                appendElement(sb, INDENT4, "welcome-file", welcomeFile);
-            }
-            sb.append("  </welcome-file-list>\n\n");
-        }
-        
-        for (ErrorPage errorPage : errorPages.values()) {
-            sb.append("  <error-page>\n");
-            if (errorPage.getExceptionType() == null) {
-                appendElement(sb, INDENT4, "error-code",
-                        Integer.toString(errorPage.getErrorCode()));
-            } else {
-                appendElement(sb, INDENT4, "exception-type",
-                        errorPage.getExceptionType());
-            }
-            appendElement(sb, INDENT4, "location", errorPage.getLocation());
-            sb.append("  </error-page>\n");
-        }
-        sb.append('\n');
-
-        if (taglibs.size() > 0 || jspPropertyGroups.size() > 0) {
-            sb.append("  <jsp-config>\n");
-            for (Map.Entry<String, String> entry : taglibs.entrySet()) {
-                sb.append("    <taglib>\n");
-                appendElement(sb, INDENT6, "taglib-uri", entry.getKey());
-                appendElement(sb, INDENT6, "taglib-location", entry.getValue());
-                sb.append("    </taglib>\n");
-            }
-            for (JspPropertyGroup jpg : jspPropertyGroups) {
-                sb.append("    <jsp-property-group>\n");
-                appendElement(sb, INDENT6, "url-pattern", jpg.getUrlPattern());
-                appendElement(sb, INDENT6, "el-ignored", jpg.getElIgnored());
-                appendElement(sb, INDENT6, "scripting-invalid",
-                        jpg.getScriptingInvalid());
-                appendElement(sb, INDENT6, "page-encoding",
-                        jpg.getPageEncoding());
-                for (String prelude : jpg.getIncludePreludes()) {
-                    appendElement(sb, INDENT6, "include-prelude", prelude);
-                }
-                for (String coda : jpg.getIncludeCodas()) {
-                    appendElement(sb, INDENT6, "include-coda", coda);
-                }
-                appendElement(sb, INDENT6, "is-xml", jpg.getIsXml());
-                appendElement(sb, INDENT6, "deferred-syntax-allowed-as-literal",
-                        jpg.getDeferredSyntax());
-                appendElement(sb, INDENT6, "trim-directive-whitespaces",
-                        jpg.getTrimWhitespace());
-                appendElement(sb, INDENT6, "default-content-type",
-                        jpg.getDefaultContentType());
-                appendElement(sb, INDENT6, "buffer", jpg.getBuffer());
-                appendElement(sb, INDENT6, "error-on-undeclared-namespace",
-                        jpg.getErrorOnUndeclaredNamespace());
-                sb.append("    </jsp-property-group>\n");
-            }
-            sb.append("  </jsp-config>\n\n");
-        }
-        
-        for (SecurityConstraint constraint : securityConstraints) {
-            sb.append("  <security-constraint>\n");
-            appendElement(sb, INDENT4, "display-name",
-                    constraint.getDisplayName());
-            for (SecurityCollection collection : constraint.findCollections()) {
-                sb.append("    <web-resource-collection>\n");
-                appendElement(sb, INDENT6, "web-resource-name",
-                        collection.getName());
-                appendElement(sb, INDENT6, "description",
-                        collection.getDescription());
-                for (String urlPattern : collection.findPatterns()) {
-                    appendElement(sb, INDENT6, "url-pattern", urlPattern);
-                }
-                for (String method : collection.findMethods()) {
-                    appendElement(sb, INDENT6, "http-method", method);
-                }
-                for (String method : collection.findOmittedMethods()) {
-                    appendElement(sb, INDENT6, "http-method-omission", method);
-                }
-                sb.append("    </web-resource-collection>\n");
-            }
-            if (constraint.findAuthRoles().length > 0) {
-                sb.append("    <auth-constraint>\n");
-                for (String role : constraint.findAuthRoles()) {
-                    appendElement(sb, INDENT6, "role-name", role);
-                }
-                sb.append("    </auth-constraint>\n");
-            }
-            if (constraint.getUserConstraint() != null) {
-                sb.append("    <user-data-constraint>\n");
-                appendElement(sb, INDENT6, "transport-guarantee",
-                        constraint.getUserConstraint());
-                sb.append("    </user-data-constraint>\n");
-            }
-            sb.append("  </security-constraint>\n");
-        }
-        sb.append('\n');
-
-        if (loginConfig != null) {
-            sb.append("  <login-config>\n");
-            appendElement(sb, INDENT4, "auth-method",
-                    loginConfig.getAuthMethod());
-            appendElement(sb,INDENT4, "realm-name",
-                    loginConfig.getRealmName());
-            if (loginConfig.getErrorPage() != null ||
-                        loginConfig.getLoginPage() != null) {
-                sb.append("    <form-login-config>\n");
-                appendElement(sb, INDENT6, "form-login-page",
-                        loginConfig.getLoginPage());
-                appendElement(sb, INDENT6, "form-error-page",
-                        loginConfig.getErrorPage());
-                sb.append("    </form-login-config>\n");
-            }
-            sb.append("  </login-config>\n\n");
-        }
-        
-        for (String roleName : securityRoles) {
-            sb.append("  <security-role>\n");
-            appendElement(sb, INDENT4, "role-name", roleName);
-            sb.append("  </security-role>\n");
-        }
-        
-        for (ContextEnvironment envEntry : envEntries.values()) {
-            sb.append("  <env-entry>\n");
-            appendElement(sb, INDENT4, "description",
-                    envEntry.getDescription());
-            appendElement(sb, INDENT4, "env-entry-name", envEntry.getName());
-            appendElement(sb, INDENT4, "env-entry-type", envEntry.getType());
-            appendElement(sb, INDENT4, "env-entry-value", envEntry.getValue());
-            // TODO mapped-name
-            for (InjectionTarget target : envEntry.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </env-entry>\n");
-        }
-        sb.append('\n');
-
-        for (ContextEjb ejbRef : ejbRefs.values()) {
-            sb.append("  <ejb-ref>\n");
-            appendElement(sb, INDENT4, "description", ejbRef.getDescription());
-            appendElement(sb, INDENT4, "ejb-ref-name", ejbRef.getName());
-            appendElement(sb, INDENT4, "ejb-ref-type", ejbRef.getType());
-            appendElement(sb, INDENT4, "home", ejbRef.getHome());
-            appendElement(sb, INDENT4, "remote", ejbRef.getRemote());
-            appendElement(sb, INDENT4, "ejb-link", ejbRef.getLink());
-            // TODO mapped-name
-            for (InjectionTarget target : ejbRef.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </ejb-ref>\n");
-        }
-        sb.append('\n');
-
-        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
-            sb.append("  <ejb-local-ref>\n");
-            appendElement(sb, INDENT4, "description",
-                    ejbLocalRef.getDescription());
-            appendElement(sb, INDENT4, "ejb-ref-name", ejbLocalRef.getName());
-            appendElement(sb, INDENT4, "ejb-ref-type", ejbLocalRef.getType());
-            appendElement(sb, INDENT4, "local-home", ejbLocalRef.getHome());
-            appendElement(sb, INDENT4, "local", ejbLocalRef.getLocal());
-            appendElement(sb, INDENT4, "ejb-link", ejbLocalRef.getLink());
-            // TODO mapped-name
-            for (InjectionTarget target : ejbLocalRef.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </ejb-local-ref>\n");
-        }
-        sb.append('\n');
-        
-        for (ContextService serviceRef : serviceRefs.values()) {
-            sb.append("  <service-ref>\n");
-            appendElement(sb, INDENT4, "description",
-                    serviceRef.getDescription());
-            appendElement(sb, INDENT4, "display-name",
-                    serviceRef.getDisplayname());
-            appendElement(sb, INDENT4, "service-ref-name",
-                    serviceRef.getName());
-            appendElement(sb, INDENT4, "service-interface",
-                    serviceRef.getInterface());
-            appendElement(sb, INDENT4, "service-ref-type",
-                    serviceRef.getType());
-            appendElement(sb, INDENT4, "wsdl-file", serviceRef.getWsdlfile());
-            appendElement(sb, INDENT4, "jaxrpc-mapping-file",
-                    serviceRef.getJaxrpcmappingfile());
-            String qname = serviceRef.getServiceqnameNamespaceURI();
-            if (qname != null) {
-                qname = qname + ":";
-            }
-            qname = qname + serviceRef.getServiceqnameLocalpart();
-            appendElement(sb, INDENT4, "service-qname", qname);
-            Iterator<String> endpointIter = serviceRef.getServiceendpoints();
-            while (endpointIter.hasNext()) {
-                String endpoint = endpointIter.next();
-                sb.append("    <port-component-ref>\n");
-                appendElement(sb, INDENT6, "service-endpoint-interface",
-                        endpoint);
-                appendElement(sb, INDENT6, "port-component-link",
-                        serviceRef.getProperty(endpoint));
-                sb.append("    </port-component-ref>\n");
-            }
-            Iterator<String> handlerIter = serviceRef.getHandlers();
-            while (handlerIter.hasNext()) {
-                String handler = handlerIter.next();
-                sb.append("    <handler>\n");
-                ContextHandler ch = serviceRef.getHandler(handler);
-                appendElement(sb, INDENT6, "handler-name", ch.getName());
-                appendElement(sb, INDENT6, "handler-class",
-                        ch.getHandlerclass());
-                sb.append("    </handler>\n");
-            }
-            // TODO handler-chains
-            // TODO mapped-name
-            for (InjectionTarget target : serviceRef.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </service-ref>\n");
-        }
-        sb.append('\n');
-        
-        for (ContextResource resourceRef : resourceRefs.values()) {
-            sb.append("  <resource-ref>\n");
-            appendElement(sb, INDENT4, "description",
-                    resourceRef.getDescription());
-            appendElement(sb, INDENT4, "res-ref-name", resourceRef.getName());
-            appendElement(sb, INDENT4, "res-type", resourceRef.getType());
-            appendElement(sb, INDENT4, "res-auth", resourceRef.getAuth());
-            appendElement(sb, INDENT4, "res-sharing-scope",
-                    resourceRef.getScope());
-            // TODO mapped-name
-            for (InjectionTarget target : resourceRef.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </resource-ref>\n");
-        }
-        sb.append('\n');
-
-        for (ContextResourceEnvRef resourceEnvRef : resourceEnvRefs.values()) {
-            sb.append("  <resource-env-ref>\n");
-            appendElement(sb, INDENT4, "description",
-                    resourceEnvRef.getDescription());
-            appendElement(sb, INDENT4, "resource-env-ref-name",
-                    resourceEnvRef.getName());
-            appendElement(sb, INDENT4, "resource-env-ref-type",
-                    resourceEnvRef.getType());
-            // TODO mapped-name
-            for (InjectionTarget target :
-                    resourceEnvRef.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </resource-env-ref>\n");
-        }
-        sb.append('\n');
-
-        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
-            sb.append("  <message-destination-ref>\n");
-            appendElement(sb, INDENT4, "description", mdr.getDescription());
-            appendElement(sb, INDENT4, "message-destination-ref-name",
-                    mdr.getName());
-            appendElement(sb, INDENT4, "message-destination-type",
-                    mdr.getType());
-            appendElement(sb, INDENT4, "message-destination-usage",
-                    mdr.getUsage());
-            appendElement(sb, INDENT4, "message-destination-link",
-                    mdr.getLink());
-            // TODO mapped-name
-            for (InjectionTarget target : mdr.getInjectionTargets()) {
-                sb.append("    <injection-target>\n");
-                appendElement(sb, INDENT6, "injection-target-class",
-                        target.getTargetClass());
-                appendElement(sb, INDENT6, "injection-target-name",
-                        target.getTargetName());
-                sb.append("    </injection-target>\n");
-            }
-            // TODO lookup-name
-            sb.append("  </message-destination-ref>\n");
-        }
-        sb.append('\n');
-
-        for (MessageDestination md : messageDestinations.values()) {
-            sb.append("  <message-destination>\n");
-            appendElement(sb, INDENT4, "description", md.getDescription());
-            appendElement(sb, INDENT4, "display-name", md.getDisplayName());
-            appendElement(sb, INDENT4, "message-destination-name",
-                    md.getName());
-            // TODO mapped-name
-            sb.append("  </message-destination>\n");
-        }
-        sb.append('\n');
-
-        if (localeEncodingMappings.size() > 0) {
-            sb.append("  <locale-encoding-mapping-list>\n");
-            for (Map.Entry<String, String> entry :
-                    localeEncodingMappings.entrySet()) {
-                sb.append("    <locale-encoding-mapping>\n");
-                appendElement(sb, INDENT6, "locale", entry.getKey());
-                appendElement(sb, INDENT6, "encoding", entry.getValue());
-                sb.append("    </locale-encoding-mapping>\n");
-            }
-            sb.append("  </locale-encoding-mapping-list>\n");
-        }
-        sb.append("</web-app>");
-        return sb.toString();
-    }
-
-    private static void appendElement(StringBuilder sb, String indent,
-            String elementName, String value) {
-        if (value == null || value.length() == 0) return;
-        sb.append(indent);
-        sb.append('<');
-        sb.append(elementName);
-        sb.append('>');
-        sb.append(escapeXml(value));
-        sb.append("</");
-        sb.append(elementName);
-        sb.append(">\n");
-    }
-
-    private static void appendElement(StringBuilder sb, String indent,
-            String elementName, Object value) {
-        if (value == null) return;
-        appendElement(sb, indent, elementName, value.toString());
-    }
-
-
-    /**
-     * Escape the 5 entities defined by XML.
-     */
-    private static String escapeXml(String s) {
-        if (s == null)
-            return null;
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < s.length(); i++) {
-            char c = s.charAt(i);
-            if (c == '<') {
-                sb.append("&lt;");
-            } else if (c == '>') {
-                sb.append("&gt;");
-            } else if (c == '\'') {
-                sb.append("&apos;");
-            } else if (c == '&') {
-                sb.append("&amp;");
-            } else if (c == '"') {
-                sb.append("&quot;");
-            } else {
-                sb.append(c);
-            }
-        }
-        return sb.toString();
-    }
-
-
-    /**
-     * Configure a {@link Context} using the stored web.xml representation.
-     *  
-     * @param context   The context to be configured
-     */
-    public void configureContext(Context context) {
-        // As far as possible, process in alphabetical order so it is easy to
-        // check everything is present
-        // Some validation depends on correct public ID
-        context.setPublicId(publicId);
-
-        // Everything else in order
-        for (String contextParam : contextParams.keySet()) {
-            context.addParameter(contextParam, contextParams.get(contextParam));
-        }
-        context.setDisplayName(displayName);
-        context.setDistributable(distributable);
-        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
-            context.getNamingResources().addLocalEjb(ejbLocalRef);
-        }
-        for (ContextEjb ejbRef : ejbRefs.values()) {
-            context.getNamingResources().addEjb(ejbRef);
-        }
-        for (ContextEnvironment environment : envEntries.values()) {
-            context.getNamingResources().addEnvironment(environment);
-        }
-        for (ErrorPage errorPage : errorPages.values()) {
-            context.addErrorPage(errorPage);
-        }
-        for (FilterDef filter : filters.values()) {
-            context.addFilterDef(filter);
-        }
-        for (FilterMap filterMap : filterMaps) {
-            context.addFilterMap(filterMap);
-        }
-        // jsp-property-group needs to be after servlet configuration
-        for (String listener : listeners) {
-            context.addApplicationListener(listener);
-        }
-        for (String locale : localeEncodingMappings.keySet()) {
-            context.addLocaleEncodingMappingParameter(locale,
-                    localeEncodingMappings.get(locale));
-        }
-        // Prevents IAE
-        if (loginConfig != null) {
-            context.setLoginConfig(loginConfig);
-        }
-        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
-            context.getNamingResources().addMessageDestinationRef(mdr);
-        }
-
-        // messageDestinations were ignored in Tomcat 6, so ignore here
-        
-        context.setIgnoreAnnotations(metadataComplete);
-        for (String extension : mimeMappings.keySet()) {
-            context.addMimeMapping(extension, mimeMappings.get(extension));
-        }
-        // Name is just used for ordering
-        for (ContextResourceEnvRef resource : resourceEnvRefs.values()) {
-            context.getNamingResources().addResourceEnvRef(resource);
-        }
-        for (ContextResource resource : resourceRefs.values()) {
-            context.getNamingResources().addResource(resource);
-        }
-        for (SecurityConstraint constraint : securityConstraints) {
-            context.addConstraint(constraint);
-        }
-        for (String role : securityRoles) {
-            context.addSecurityRole(role);
-        }
-        for (ContextService service : serviceRefs.values()) {
-            context.getNamingResources().addService(service);
-        }
-        for (ServletDef servlet : servlets.values()) {
-            Wrapper wrapper = context.createWrapper();
-            // Description is ignored
-            // Display name is ignored
-            // Icons are ignored
-            // Only set this if it is non-null else every servlet will get
-            // marked as the JSP servlet
-            String jspFile = servlet.getJspFile();
-            if (jspFile != null) {
-                wrapper.setJspFile(jspFile);
-            }
-            if (servlet.getLoadOnStartup() != null) {
-                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
-            }
-            if (servlet.getEnabled() != null) {
-                wrapper.setEnabled(servlet.getEnabled().booleanValue());
-            }
-            wrapper.setName(servlet.getServletName());
-            Map<String,String> params = servlet.getParameterMap(); 
-            for (String param : params.keySet()) {
-                wrapper.addInitParameter(param, params.get(param));
-            }
-            wrapper.setRunAs(servlet.getRunAs());
-            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
-            for (SecurityRoleRef roleRef : roleRefs) {
-                wrapper.addSecurityReference(
-                        roleRef.getName(), roleRef.getLink());
-            }
-            wrapper.setServletClass(servlet.getServletClass());
-            MultipartDef multipartdef = servlet.getMultipartDef();
-            if (multipartdef != null) {
-                if (multipartdef.getMaxFileSize() != null &&
-                        multipartdef.getMaxRequestSize()!= null &&
-                        multipartdef.getFileSizeThreshold() != null) {
-                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
-                            multipartdef.getLocation(),
-                            Long.parseLong(multipartdef.getMaxFileSize()),
-                            Long.parseLong(multipartdef.getMaxRequestSize()),
-                            Integer.parseInt(
-                                    multipartdef.getFileSizeThreshold())));
-                } else {
-                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
-                            multipartdef.getLocation()));
-                }
-            }
-            if (servlet.getAsyncSupported() != null) {
-                wrapper.setAsyncSupported(
-                        servlet.getAsyncSupported().booleanValue());
-            }
-            context.addChild(wrapper);
-        }
-        for (String pattern : servletMappings.keySet()) {
-            context.addServletMapping(pattern, servletMappings.get(pattern));
-        }
-        if (sessionConfig != null) {
-            if (sessionConfig.getSessionTimeout() != null) {
-                context.setSessionTimeout(
-                        sessionConfig.getSessionTimeout().intValue());
-            }
-            SessionCookieConfig scc =
-                context.getServletContext().getSessionCookieConfig();
-            scc.setName(sessionConfig.getCookieName());
-            scc.setDomain(sessionConfig.getCookieDomain());
-            scc.setPath(sessionConfig.getCookiePath());
-            scc.setComment(sessionConfig.getCookieComment());
-            if (sessionConfig.getCookieHttpOnly() != null) {
-                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
-            }
-            if (sessionConfig.getCookieSecure() != null) {
-                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
-            }
-            if (sessionConfig.getCookieMaxAge() != null) {
-                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
-            }
-            if (sessionConfig.getSessionTrackingModes().size() > 0) {
-                context.getServletContext().setSessionTrackingModes(
-                        sessionConfig.getSessionTrackingModes());
-            }
-        }
-        for (String uri : taglibs.keySet()) {
-            context.addTaglib(uri, taglibs.get(uri));
-        }
-        
-        // Context doesn't use version directly
-        
-        for (String welcomeFile : welcomeFiles) {
-            context.addWelcomeFile(welcomeFile);
-        }
-
-        // Do this last as it depends on servlets
-        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
-            context.addJspMapping(jspPropertyGroup.getUrlPattern());
-        }
-    }
-    
-    /**
-     * Merge the supplied web fragments into this main web.xml.
-     * 
-     * @param fragments     The fragments to merge in
-     * @return <code>true</code> if merge is successful, else
-     *         <code>false</code>
-     */
-    public boolean merge(Set<WebXml> fragments) {
-        // As far as possible, process in alphabetical order so it is easy to
-        // check everything is present
-        
-        // Merge rules vary from element to element. See SRV.8.2.3
-
-        WebXml temp = new WebXml();
-        Map<String,Boolean> mergeInjectionFlags =
-            new HashMap<String, Boolean>();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeMap(fragment.getContextParams(), contextParams,
-                    temp.getContextParams(), fragment, "Context Parameter")) {
-                return false;
-            }
-        }
-        contextParams.putAll(temp.getContextParams());
-
-        if (displayName == null) {
-            for (WebXml fragment : fragments) {
-                String value = fragment.getDisplayName(); 
-                if (value != null) {
-                    if (temp.getDisplayName() == null) {
-                        temp.setDisplayName(value);
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictDisplayName",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            displayName = temp.getDisplayName();
-        }
-
-        if (distributable) {
-            for (WebXml fragment : fragments) {
-                if (!fragment.isDistributable()) {
-                    distributable = false;
-                    break;
-                }
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getEjbLocalRefs(), ejbLocalRefs,
-                    temp.getEjbLocalRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        ejbLocalRefs.putAll(temp.getEjbLocalRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getEjbRefs(), ejbRefs,
-                    temp.getEjbRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        ejbRefs.putAll(temp.getEjbRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getEnvEntries(), envEntries,
-                    temp.getEnvEntries(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        envEntries.putAll(temp.getEnvEntries());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeMap(fragment.getErrorPages(), errorPages,
-                    temp.getErrorPages(), fragment, "Error Page")) {
-                return false;
-            }
-        }
-        errorPages.putAll(temp.getErrorPages());
-
-        for (WebXml fragment : fragments) {
-            for (FilterMap filterMap : fragment.getFilterMappings()) {
-                // Always additive
-                addFilterMapping(filterMap);
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            for (Map.Entry<String,FilterDef> entry :
-                    fragment.getFilters().entrySet()) {
-                if (filters.containsKey(entry.getKey())) {
-                    mergeFilter(entry.getValue(),
-                            filters.get(entry.getKey()), false);
-                } else {
-                    if (temp.getFilters().containsKey(entry.getKey())) {
-                        if (!(mergeFilter(entry.getValue(),
-                                temp.getFilters().get(entry.getKey()), true))) {
-                            log.error(sm.getString(
-                                    "webXml.mergeConflictFilter",
-                                    entry.getKey(),
-                                    fragment.getName(),
-                                    fragment.getURL()));
-    
-                            return false;
-                        }
-                    } else {
-                        temp.getFilters().put(entry.getKey(), entry.getValue());
-                    }
-                }
-            }
-        }
-        filters.putAll(temp.getFilters());
-
-        for (WebXml fragment : fragments) {
-            for (JspPropertyGroup jspPropertyGroup :
-                    fragment.getJspPropertyGroups()) {
-                // Always additive
-                addJspPropertyGroup(jspPropertyGroup);
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            for (String listener : fragment.getListeners()) {
-                // Always additive
-                addListener(listener);
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            if (!mergeMap(fragment.getLocalEncodingMappings(),
-                    localeEncodingMappings, temp.getLocalEncodingMappings(),
-                    fragment, "Locale Encoding Mapping")) {
-                return false;
-            }
-        }
-        localeEncodingMappings.putAll(temp.getLocalEncodingMappings());
-
-        if (getLoginConfig() == null) {
-            LoginConfig tempLoginConfig = null;
-            for (WebXml fragment : fragments) {
-                LoginConfig fragmentLoginConfig = fragment.loginConfig;
-                if (fragmentLoginConfig != null) {
-                    if (tempLoginConfig == null ||
-                            fragmentLoginConfig.equals(tempLoginConfig)) {
-                        tempLoginConfig = fragmentLoginConfig;
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictLoginConfig",
-                                fragment.getName(),
-                                fragment.getURL()));
-                    }
-                }
-            }
-            loginConfig = tempLoginConfig;
-        }
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getMessageDestinationRefs(), messageDestinationRefs,
-                    temp.getMessageDestinationRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        messageDestinationRefs.putAll(temp.getMessageDestinationRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getMessageDestinations(), messageDestinations,
-                    temp.getMessageDestinations(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        messageDestinations.putAll(temp.getMessageDestinations());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeMap(fragment.getMimeMappings(), mimeMappings,
-                    temp.getMimeMappings(), fragment, "Mime Mapping")) {
-                return false;
-            }
-        }
-        mimeMappings.putAll(temp.getMimeMappings());
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getResourceEnvRefs(), resourceEnvRefs,
-                    temp.getResourceEnvRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        resourceEnvRefs.putAll(temp.getResourceEnvRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getResourceRefs(), resourceRefs,
-                    temp.getResourceRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        resourceRefs.putAll(temp.getResourceRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            for (SecurityConstraint constraint : fragment.getSecurityConstraints()) {
-                // Always additive
-                addSecurityConstraint(constraint);
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            for (String role : fragment.getSecurityRoles()) {
-                // Always additive
-                addSecurityRole(role);
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            if (!mergeResourceMap(fragment.getServiceRefs(), serviceRefs,
-                    temp.getServiceRefs(), mergeInjectionFlags, fragment)) {
-                return false;
-            }
-        }
-        serviceRefs.putAll(temp.getServiceRefs());
-        mergeInjectionFlags.clear();
-
-        for (WebXml fragment : fragments) {
-            for (Map.Entry<String,String> mapping :
-                    fragment.getServletMappings().entrySet()) {
-                // Always additive
-                addServletMapping(mapping.getKey(), mapping.getValue());
-            }
-        }
-
-        for (WebXml fragment : fragments) {
-            for (Map.Entry<String,ServletDef> entry :
-                    fragment.getServlets().entrySet()) {
-                if (servlets.containsKey(entry.getKey())) {
-                    mergeServlet(entry.getValue(),
-                            servlets.get(entry.getKey()), false);
-                } else {
-                    if (temp.getServlets().containsKey(entry.getKey())) {
-                        if (!(mergeServlet(entry.getValue(),
-                                temp.getServlets().get(entry.getKey()), true))) {
-                            log.error(sm.getString(
-                                    "webXml.mergeConflictServlet",
-                                    entry.getKey(),
-                                    fragment.getName(),
-                                    fragment.getURL()));
-    
-                            return false;
-                        }
-                    } else {
-                        temp.getServlets().put(entry.getKey(), entry.getValue());
-                    }
-                }
-            }
-        }
-        servlets.putAll(temp.getServlets());
-        
-        if (sessionConfig.getSessionTimeout() == null) {
-            for (WebXml fragment : fragments) {
-                Integer value = fragment.getSessionConfig().getSessionTimeout();
-                if (value != null) {
-                    if (temp.getSessionConfig().getSessionTimeout() == null) {
-                        temp.getSessionConfig().setSessionTimeout(value.toString());
-                    } else if (value.equals(
-                            temp.getSessionConfig().getSessionTimeout())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionTimeout",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            if (temp.getSessionConfig().getSessionTimeout() != null) {
-                sessionConfig.setSessionTimeout(
-                        temp.getSessionConfig().getSessionTimeout().toString());
-            }
-        }
-        
-        if (sessionConfig.getCookieName() == null) {
-            for (WebXml fragment : fragments) {
-                String value = fragment.getSessionConfig().getCookieName();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieName() == null) {
-                        temp.getSessionConfig().setCookieName(value);
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieName())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieName",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            sessionConfig.setCookieName(
-                    temp.getSessionConfig().getCookieName());
-        }
-        if (sessionConfig.getCookieDomain() == null) {
-            for (WebXml fragment : fragments) {
-                String value = fragment.getSessionConfig().getCookieDomain();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieDomain() == null) {
-                        temp.getSessionConfig().setCookieDomain(value);
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieDomain())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieDomain",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            sessionConfig.setCookieDomain(
-                    temp.getSessionConfig().getCookieDomain());
-        }
-        if (sessionConfig.getCookiePath() == null) {
-            for (WebXml fragment : fragments) {
-                String value = fragment.getSessionConfig().getCookiePath();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookiePath() == null) {
-                        temp.getSessionConfig().setCookiePath(value);
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookiePath())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookiePath",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            sessionConfig.setCookiePath(
-                    temp.getSessionConfig().getCookiePath());
-        }
-        if (sessionConfig.getCookieComment() == null) {
-            for (WebXml fragment : fragments) {
-                String value = fragment.getSessionConfig().getCookieComment();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieComment() == null) {
-                        temp.getSessionConfig().setCookieComment(value);
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieComment())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieComment",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            sessionConfig.setCookieComment(
-                    temp.getSessionConfig().getCookieComment());
-        }
-        if (sessionConfig.getCookieHttpOnly() == null) {
-            for (WebXml fragment : fragments) {
-                Boolean value = fragment.getSessionConfig().getCookieHttpOnly();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieHttpOnly() == null) {
-                        temp.getSessionConfig().setCookieHttpOnly(value.toString());
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieHttpOnly())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieHttpOnly",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            if (temp.getSessionConfig().getCookieHttpOnly() != null) {
-                sessionConfig.setCookieHttpOnly(
-                        temp.getSessionConfig().getCookieHttpOnly().toString());
-            }
-        }
-        if (sessionConfig.getCookieSecure() == null) {
-            for (WebXml fragment : fragments) {
-                Boolean value = fragment.getSessionConfig().getCookieSecure();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieSecure() == null) {
-                        temp.getSessionConfig().setCookieSecure(value.toString());
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieSecure())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieSecure",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            if (temp.getSessionConfig().getCookieSecure() != null) {
-                sessionConfig.setCookieSecure(
-                        temp.getSessionConfig().getCookieSecure().toString());
-            }
-        }
-        if (sessionConfig.getCookieMaxAge() == null) {
-            for (WebXml fragment : fragments) {
-                Integer value = fragment.getSessionConfig().getCookieMaxAge();
-                if (value != null) {
-                    if (temp.getSessionConfig().getCookieMaxAge() == null) {
-                        temp.getSessionConfig().setCookieMaxAge(value.toString());
-                    } else if (value.equals(
-                            temp.getSessionConfig().getCookieMaxAge())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionCookieMaxAge",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            if (temp.getSessionConfig().getCookieMaxAge() != null) {
-                sessionConfig.setCookieMaxAge(
-                        temp.getSessionConfig().getCookieMaxAge().toString());
-            }
-        }
-
-        if (sessionConfig.getSessionTrackingModes().size() == 0) {
-            for (WebXml fragment : fragments) {
-                EnumSet<SessionTrackingMode> value =
-                    fragment.getSessionConfig().getSessionTrackingModes();
-                if (value.size() > 0) {
-                    if (temp.getSessionConfig().getSessionTrackingModes().size() == 0) {
-                        temp.getSessionConfig().getSessionTrackingModes().addAll(value);
-                    } else if (value.equals(
-                            temp.getSessionConfig().getSessionTrackingModes())) {
-                        // Fragments use same value - no conflict
-                    } else {
-                        log.error(sm.getString(
-                                "webXml.mergeConflictSessionTrackingMode",
-                                fragment.getName(),
-                                fragment.getURL()));
-                        return false;
-                    }
-                }
-            }
-            sessionConfig.getSessionTrackingModes().addAll(
-                    temp.getSessionConfig().getSessionTrackingModes());
-        }
-        
-        for (WebXml fragment : fragments) {
-            if (!mergeMap(fragment.getTaglibs(), taglibs,
-                    temp.getTaglibs(), fragment, "Taglibs")) {
-                return false;
-            }
-        }
-        taglibs.putAll(temp.getTaglibs());
-
-        for (WebXml fragment : fragments) {
-            for (String welcomeFile : fragment.getWelcomeFiles()) {
-                // Always additive
-                addWelcomeFile(welcomeFile);
-            }
-        }
-
-        return true;
-    }
-    
-    private static <T extends ResourceBase> boolean mergeResourceMap(
-            Map<String, T> fragmentResources, Map<String, T> mainResources,
-            Map<String, T> tempResources,
-            Map<String,Boolean> mergeInjectionFlags, WebXml fragment) {
-        for (T resource : fragmentResources.values()) {
-            String resourceName = resource.getName();
-            boolean mergeInjectionFlag = false;
-            if (mainResources.containsKey(resourceName)) {
-                if (mergeInjectionFlags.containsKey(resourceName)) {
-                    mergeInjectionFlag =
-                        mergeInjectionFlags.get(resourceName).booleanValue(); 
-                } else {
-                    if (mainResources.get(
-                            resourceName).getInjectionTargets().size() == 0) {
-                        mergeInjectionFlag = true;
-                    }
-                    mergeInjectionFlags.put(resourceName,
-                            Boolean.valueOf(mergeInjectionFlag));
-                }
-                if (mergeInjectionFlag) {
-                    mainResources.get(resourceName).getInjectionTargets().addAll(
-                            resource.getInjectionTargets());
-                }
-            } else {
-                // Not defined in main web.xml
-                if (tempResources.containsKey(resourceName)) {
-                    log.error(sm.getString(
-                            "webXml.mergeConflictResource",
-                            resourceName,
-                            fragment.getName(),
-                            fragment.getURL()));
-                    return false;
-                } 
-                tempResources.put(resourceName, resource);
-            }
-        }
-        return true;
-    }
-    
-    private static <T> boolean mergeMap(Map<String,T> fragmentMap,
-            Map<String,T> mainMap, Map<String,T> tempMap, WebXml fragment,
-            String mapName) {
-        for (String key : fragmentMap.keySet()) {
-            if (!mainMap.containsKey(key)) {
-                // Not defined in main web.xml
-                T value = fragmentMap.get(key);
-                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;
-    }
-    
-    private static boolean mergeFilter(FilterDef src, FilterDef dest,
-            boolean failOnConflict) {
-        if (dest.getAsyncSupported() == null) {
-            dest.setAsyncSupported(src.getAsyncSupported());
-        } else if (src.getAsyncSupported() != null) {
-            if (failOnConflict &&
-                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
-                return false;
-            }
-        }
-
-        if (dest.getFilterClass()  == null) {
-            dest.setFilterClass(src.getFilterClass());
-        } else if (src.getFilterClass() != null) {
-            if (failOnConflict &&
-                    !src.getFilterClass().equals(dest.getFilterClass())) {
-                return false;
-            }
-        }
-        
-        for (Map.Entry<String,String> srcEntry :
-                src.getParameterMap().entrySet()) {
-            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
-                if (failOnConflict && !dest.getParameterMap().get(
-                        srcEntry.getKey()).equals(srcEntry.getValue())) {
-                    return false;
-                }
-            } else {
-                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
-            }
-        }
-        return true;
-    }
-    
-    private static boolean mergeServlet(ServletDef src, ServletDef dest,
-            boolean failOnConflict) {
-        // These tests should be unnecessary...
-        if (dest.getServletClass() != null && dest.getJspFile() != null) {
-            return false;
-        }
-        if (src.getServletClass() != null && src.getJspFile() != null) {
-            return false;
-        }
-        
-        
-        if (dest.getServletClass() == null && dest.getJspFile() == null) {
-            dest.setServletClass(src.getServletClass());
-            dest.setJspFile(src.getJspFile());
-        } else if (failOnConflict) {
-            if (src.getServletClass() != null &&
-                    (dest.getJspFile() != null ||
-                            !src.getServletClass().equals(dest.getServletClass()))) {
-                return false;
-            }
-            if (src.getJspFile() != null &&
-                    (dest.getServletClass() != null ||
-                            !src.getJspFile().equals(dest.getJspFile()))) {
-                return false;
-            }
-        }
-        
-        // Additive
-        for (SecurityRoleRef securityRoleRef : src.getSecurityRoleRefs()) {
-            dest.addSecurityRoleRef(securityRoleRef);
-        }
-        
-        if (dest.getLoadOnStartup() == null) {
-            dest.setLoadOnStartup(src.getLoadOnStartup().toString());
-        } else if (src.getLoadOnStartup() != null) {
-            if (failOnConflict &&
-                    !src.getLoadOnStartup().equals(dest.getLoadOnStartup())) {
-                return false;
-            }
-        }
-        
-        if (dest.getEnabled() == null) {
-            dest.setEnabled(src.getEnabled().toString());
-        } else if (src.getEnabled() != null) {
-            if (failOnConflict &&
-                    !src.getEnabled().equals(dest.getEnabled())) {
-                return false;
-            }
-        }
-        
-        for (Map.Entry<String,String> srcEntry :
-                src.getParameterMap().entrySet()) {
-            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
-                if (failOnConflict && !dest.getParameterMap().get(
-                        srcEntry.getKey()).equals(srcEntry.getValue())) {
-                    return false;
-                }
-            } else {
-                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
-            }
-        }
-        
-        if (dest.getMultipartDef() == null) {
-            dest.setMultipartDef(src.getMultipartDef());
-        } else if (src.getMultipartDef() != null) {
-            return mergeMultipartDef(src.getMultipartDef(),
-                    dest.getMultipartDef(), failOnConflict);
-        }
-        
-        if (dest.getAsyncSupported() == null) {
-            dest.setAsyncSupported(src.getAsyncSupported().toString());
-        } else if (src.getAsyncSupported() != null) {
-            if (failOnConflict &&
-                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
-                return false;
-            }
-        }
-        
-        return true;
-    }
-
-    private static boolean mergeMultipartDef(MultipartDef src, MultipartDef dest,
-            boolean failOnConflict) {
-
-        if (dest.getLocation() == null) {
-            dest.setLocation(src.getLocation());
-        } else if (src.getLocation() != null) {
-            if (failOnConflict &&
-                    !src.getLocation().equals(dest.getLocation())) {
-                return false;
-            }
-        }
-
-        if (dest.getFileSizeThreshold() == null) {
-            dest.setFileSizeThreshold(src.getFileSizeThreshold());
-        } else if (src.getFileSizeThreshold() != null) {
-            if (failOnConflict &&
-                    !src.getFileSizeThreshold().equals(
-                            dest.getFileSizeThreshold())) {
-                return false;
-            }
-        }
-
-        if (dest.getMaxFileSize() == null) {
-            dest.setMaxFileSize(src.getMaxFileSize());
-        } else if (src.getLocation() != null) {
-            if (failOnConflict &&
-                    !src.getMaxFileSize().equals(dest.getMaxFileSize())) {
-                return false;
-            }
-        }
-
-        if (dest.getMaxRequestSize() == null) {
-            dest.setMaxRequestSize(src.getMaxRequestSize());
-        } else if (src.getMaxRequestSize() != null) {
-            if (failOnConflict &&
-                    !src.getMaxRequestSize().equals(
-                            dest.getMaxRequestSize())) {
-                return false;
-            }
-        }
-
-        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
-     * Servlet spec.
-     * 
-     * @param application   The application web.xml file
-     * @param fragments     The map of fragment names to web fragments
-     * @return Ordered list of web-fragment.xml files to process
-     */
-    protected static Set<WebXml> orderWebFragments(WebXml application,
-            Map<String,WebXml> fragments) {
-
-        Set<WebXml> orderedFragments = new LinkedHashSet<WebXml>();
-        
-        boolean absoluteOrdering =
-            (application.getAbsoluteOrdering() != null);
-        
-        if (absoluteOrdering) {
-            // Only those fragments listed should be processed
-            Set<String> requestedOrder = application.getAbsoluteOrdering();
-            
-            for (String requestedName : requestedOrder) {
-                if (WebXml.ORDER_OTHERS.equals(requestedName)) {
-                    // Add all fragments not named explicitly at this point
-                    for (String name : fragments.keySet()) {
-                        if (!requestedOrder.contains(name)) {
-                            WebXml fragment = fragments.get(name);
-                            if (fragment != null) {
-                                orderedFragments.add(fragment);
-                            }
-                        }
-                    }
-                } else {
-                    WebXml fragment = fragments.get(requestedName);
-                    if (fragment != null) {
-                        orderedFragments.add(fragment);
-                    }
-                }
-            }
-        } else {
-            List<String> order = new LinkedList<String>();
-            // Start by adding all fragments - order doesn't matter
-            order.addAll(fragments.keySet());
-            
-            // Now go through and move elements to start/end depending on if
-            // they specify others
-            for (WebXml fragment : fragments.values()) {
-                String name = fragment.getName();
-                if (fragment.getBeforeOrder().contains(WebXml.ORDER_OTHERS)) {
-                    // Move to beginning
-                    order.remove(name);
-                    order.add(0, name);
-                } else if (fragment.getAfterOrder().contains(WebXml.ORDER_OTHERS)) {
-                    // Move to end
-                    order.remove(name);
-                    order.add(name);
-                }
-            }
-            
-            // Now apply remaining ordering
-            for (WebXml fragment : fragments.values()) {
-                String name = fragment.getName();
-                for (String before : fragment.getBeforeOrder()) {
-                    if (!before.equals(WebXml.ORDER_OTHERS) &&
-                            order.contains(before) &&
-                            order.indexOf(before) < order.indexOf(name)) {
-                        order.remove(name);
-                        order.add(order.indexOf(before), name);
-                    }
-                }
-                for (String after : fragment.getAfterOrder()) {
-                    if (!after.equals(WebXml.ORDER_OTHERS) &&
-                            order.contains(after) &&
-                            order.indexOf(after) > order.indexOf(name)) {
-                        order.remove(name);
-                        order.add(order.indexOf(after) + 1, name);
-                    }
-                }
-            }
-            
-            // Finally check ordering was applied correctly - if there are
-            // errors then that indicates circular references
-            for (WebXml fragment : fragments.values()) {
-                String name = fragment.getName();
-                for (String before : fragment.getBeforeOrder()) {
-                    if (!before.equals(WebXml.ORDER_OTHERS) &&
-                            order.contains(before) &&
-                            order.indexOf(before) < order.indexOf(name)) {
-                        throw new IllegalArgumentException(sm.getString(""));
-                    }
-                }
-                for (String after : fragment.getAfterOrder()) {
-                    if (!after.equals(WebXml.ORDER_OTHERS) &&
-                            order.contains(after) &&
-                            order.indexOf(after) > order.indexOf(name)) {
-                        throw new IllegalArgumentException();
-                    }
-                }
-            }
-            
-            // Build the ordered list
-            for (String name : order) {
-                orderedFragments.add(fragments.get(name));
-            }
-        }
-        
-        return orderedFragments;
-    }
-
-}    
index fa9b940..d218581 100644 (file)
@@ -23,10 +23,10 @@ import junit.framework.TestSuite;
 import org.apache.catalina.connector.TestKeepAliveCount;
 import org.apache.catalina.connector.TestRequest;
 import org.apache.catalina.core.TestStandardContext;
+import org.apache.catalina.deploy.TestWebXml;
 import org.apache.catalina.ha.session.TestSerializablePrincipal;
 import org.apache.catalina.startup.TestTomcat;
 import org.apache.catalina.startup.TestTomcatSSL;
-import org.apache.catalina.startup.TestWebXml;
 import org.apache.el.TestELEvaluation;
 import org.apache.el.TestELInJsp;
 import org.apache.el.lang.TestELSupport;
diff --git a/test/org/apache/catalina/deploy/TestWebXml.java b/test/org/apache/catalina/deploy/TestWebXml.java
new file mode 100644 (file)
index 0000000..3174dda
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ *  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.deploy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.catalina.deploy.WebXml;
+import org.apache.catalina.startup.ContextConfig;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for {@link ContextConfig}.
+ */
+public class TestWebXml extends TestCase {
+    private WebXml app;
+    private WebXml a;
+    private WebXml b;
+    private WebXml c;
+    private WebXml d;
+    private WebXml e;
+    private WebXml f;
+    private Map<String,WebXml> fragments;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        app = new WebXml();
+        a = new WebXml();
+        a.setName("a");
+        b = new WebXml();
+        b.setName("b");
+        c = new WebXml();
+        c.setName("c");
+        d = new WebXml();
+        d.setName("d");
+        e = new WebXml();
+        e.setName("e");
+        f = new WebXml();
+        f.setName("f");
+        fragments = new HashMap<String,WebXml>();
+        fragments.put("a",a);
+        fragments.put("b",b);
+        fragments.put("c",c);
+        fragments.put("d",d);
+        fragments.put("e",e);
+        fragments.put("f",f);
+    }
+
+    public void testOrderWebFragmentsAbsolute() {
+        app.addAbsoluteOrdering("c");
+        app.addAbsoluteOrdering("a");
+        app.addAbsoluteOrdering("b");
+        app.addAbsoluteOrdering("e");
+        app.addAbsoluteOrdering("d");
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        assertEquals(c,iter.next());
+        assertEquals(a,iter.next());
+        assertEquals(b,iter.next());
+        assertEquals(e,iter.next());
+        assertEquals(d,iter.next());
+        assertFalse(iter.hasNext());
+    }
+
+    public void testOrderWebFragmentsAbsolutePartial() {
+        app.addAbsoluteOrdering("c");
+        app.addAbsoluteOrdering("a");
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        assertEquals(c,iter.next());
+        assertEquals(a,iter.next());
+        assertFalse(iter.hasNext());
+    }
+
+    public void testOrderWebFragmentsAbsoluteOthersStart() {
+        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
+        app.addAbsoluteOrdering("b");
+        app.addAbsoluteOrdering("d");
+        
+        Set<WebXml> others = new HashSet<WebXml>();
+        others.add(a);
+        others.add(c);
+        others.add(e);
+        others.add(f);
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        while (others.size() > 0) {
+            WebXml o = iter.next();
+            assertTrue(others.contains(o));
+            others.remove(o);
+        }
+        assertEquals(b,iter.next());
+        assertEquals(d,iter.next());
+        assertFalse(iter.hasNext());
+    }
+
+    public void testOrderWebFragmentsAbsoluteOthersMiddle() {
+        app.addAbsoluteOrdering("b");
+        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
+        app.addAbsoluteOrdering("d");
+        
+        Set<WebXml> others = new HashSet<WebXml>();
+        others.add(a);
+        others.add(c);
+        others.add(e);
+        others.add(f);
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+
+        Iterator<WebXml> iter = ordered.iterator();
+        assertEquals(b,iter.next());
+
+        while (others.size() > 0) {
+            WebXml o = iter.next();
+            assertTrue(others.contains(o));
+            others.remove(o);
+        }
+        assertEquals(d,iter.next());
+        assertFalse(iter.hasNext());
+    }
+
+    public void testOrderWebFragmentsAbsoluteOthersEnd() {
+        app.addAbsoluteOrdering("b");
+        app.addAbsoluteOrdering("d");
+        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
+        
+        Set<WebXml> others = new HashSet<WebXml>();
+        others.add(a);
+        others.add(c);
+        others.add(e);
+        others.add(f);
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+
+        Iterator<WebXml> iter = ordered.iterator();
+        assertEquals(b,iter.next());
+        assertEquals(d,iter.next());
+
+        while (others.size() > 0) {
+            WebXml o = iter.next();
+            assertTrue(others.contains(o));
+            others.remove(o);
+        }
+        assertFalse(iter.hasNext());
+    }
+
+    public void testOrderWebFragmentsRelative1() {
+        // First example from servlet spec
+        a.addAfterOrderOthers();
+        a.addAfterOrder("c");
+        b.addBeforeOrderOthers();
+        c.addAfterOrderOthers();
+        f.addBeforeOrderOthers();
+        f.addBeforeOrder("b");
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        assertEquals(f,iter.next());
+        assertEquals(b,iter.next());
+        assertEquals(d,iter.next());
+        assertEquals(e,iter.next());
+        assertEquals(c,iter.next());
+        assertEquals(a,iter.next());
+    }
+    
+    public void testOrderWebFragmentsRelative2() {
+        // Second example - use fragment a for no-id fragment
+        a.addAfterOrderOthers();
+        a.addBeforeOrder("c");
+        b.addBeforeOrderOthers();
+        d.addAfterOrderOthers();
+        e.addBeforeOrderOthers();
+        
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        // A number of orders are possible but the algorithm is deterministic
+        // and this order is valid. If this fails after a change to the
+        // algorithm, then check to see if the new order is also valid.
+        assertEquals(b,iter.next());
+        assertEquals(e,iter.next());
+        assertEquals(f,iter.next());
+        assertEquals(a,iter.next());
+        assertEquals(c,iter.next());
+        assertEquals(d,iter.next());
+    }
+    
+    public void testOrderWebFragmentsRelative3() {
+        // Third example from spec
+        a.addAfterOrder("b");
+        c.addBeforeOrderOthers();
+        fragments.remove("e");
+        fragments.remove("f");
+
+        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
+        
+        Iterator<WebXml> iter = ordered.iterator();
+        // A number of orders are possible but the algorithm is deterministic
+        // and this order is valid. If this fails after a change to the
+        // algorithm, then check to see if the new order is also valid.
+        assertEquals(c,iter.next());
+        assertEquals(d,iter.next());
+        assertEquals(b,iter.next());
+        assertEquals(a,iter.next());
+    }
+    
+    public void testOrderWebFragmentsrelativeCircular() {
+        a.addBeforeOrder("b");
+        b.addBeforeOrder("a");
+
+        Exception exception = null;
+        
+        try {
+            WebXml.orderWebFragments(app, fragments);
+        } catch (Exception e1) {
+            exception = e1;
+        }
+        
+        assertTrue(exception instanceof IllegalArgumentException);
+    }
+}
diff --git a/test/org/apache/catalina/startup/TestWebXml.java b/test/org/apache/catalina/startup/TestWebXml.java
deleted file mode 100644 (file)
index 6952acb..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- *  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.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import junit.framework.TestCase;
-
-/**
- * Test case for {@link ContextConfig}.
- */
-public class TestWebXml extends TestCase {
-    private WebXml app;
-    private WebXml a;
-    private WebXml b;
-    private WebXml c;
-    private WebXml d;
-    private WebXml e;
-    private WebXml f;
-    private Map<String,WebXml> fragments;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        app = new WebXml();
-        a = new WebXml();
-        a.setName("a");
-        b = new WebXml();
-        b.setName("b");
-        c = new WebXml();
-        c.setName("c");
-        d = new WebXml();
-        d.setName("d");
-        e = new WebXml();
-        e.setName("e");
-        f = new WebXml();
-        f.setName("f");
-        fragments = new HashMap<String,WebXml>();
-        fragments.put("a",a);
-        fragments.put("b",b);
-        fragments.put("c",c);
-        fragments.put("d",d);
-        fragments.put("e",e);
-        fragments.put("f",f);
-    }
-
-    public void testOrderWebFragmentsAbsolute() {
-        app.addAbsoluteOrdering("c");
-        app.addAbsoluteOrdering("a");
-        app.addAbsoluteOrdering("b");
-        app.addAbsoluteOrdering("e");
-        app.addAbsoluteOrdering("d");
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        assertEquals(c,iter.next());
-        assertEquals(a,iter.next());
-        assertEquals(b,iter.next());
-        assertEquals(e,iter.next());
-        assertEquals(d,iter.next());
-        assertFalse(iter.hasNext());
-    }
-
-    public void testOrderWebFragmentsAbsolutePartial() {
-        app.addAbsoluteOrdering("c");
-        app.addAbsoluteOrdering("a");
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        assertEquals(c,iter.next());
-        assertEquals(a,iter.next());
-        assertFalse(iter.hasNext());
-    }
-
-    public void testOrderWebFragmentsAbsoluteOthersStart() {
-        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
-        app.addAbsoluteOrdering("b");
-        app.addAbsoluteOrdering("d");
-        
-        Set<WebXml> others = new HashSet<WebXml>();
-        others.add(a);
-        others.add(c);
-        others.add(e);
-        others.add(f);
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        while (others.size() > 0) {
-            WebXml o = iter.next();
-            assertTrue(others.contains(o));
-            others.remove(o);
-        }
-        assertEquals(b,iter.next());
-        assertEquals(d,iter.next());
-        assertFalse(iter.hasNext());
-    }
-
-    public void testOrderWebFragmentsAbsoluteOthersMiddle() {
-        app.addAbsoluteOrdering("b");
-        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
-        app.addAbsoluteOrdering("d");
-        
-        Set<WebXml> others = new HashSet<WebXml>();
-        others.add(a);
-        others.add(c);
-        others.add(e);
-        others.add(f);
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-
-        Iterator<WebXml> iter = ordered.iterator();
-        assertEquals(b,iter.next());
-
-        while (others.size() > 0) {
-            WebXml o = iter.next();
-            assertTrue(others.contains(o));
-            others.remove(o);
-        }
-        assertEquals(d,iter.next());
-        assertFalse(iter.hasNext());
-    }
-
-    public void testOrderWebFragmentsAbsoluteOthersEnd() {
-        app.addAbsoluteOrdering("b");
-        app.addAbsoluteOrdering("d");
-        app.addAbsoluteOrdering(WebXml.ORDER_OTHERS);
-        
-        Set<WebXml> others = new HashSet<WebXml>();
-        others.add(a);
-        others.add(c);
-        others.add(e);
-        others.add(f);
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-
-        Iterator<WebXml> iter = ordered.iterator();
-        assertEquals(b,iter.next());
-        assertEquals(d,iter.next());
-
-        while (others.size() > 0) {
-            WebXml o = iter.next();
-            assertTrue(others.contains(o));
-            others.remove(o);
-        }
-        assertFalse(iter.hasNext());
-    }
-
-    public void testOrderWebFragmentsRelative1() {
-        // First example from servlet spec
-        a.addAfterOrderOthers();
-        a.addAfterOrder("c");
-        b.addBeforeOrderOthers();
-        c.addAfterOrderOthers();
-        f.addBeforeOrderOthers();
-        f.addBeforeOrder("b");
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        assertEquals(f,iter.next());
-        assertEquals(b,iter.next());
-        assertEquals(d,iter.next());
-        assertEquals(e,iter.next());
-        assertEquals(c,iter.next());
-        assertEquals(a,iter.next());
-    }
-    
-    public void testOrderWebFragmentsRelative2() {
-        // Second example - use fragment a for no-id fragment
-        a.addAfterOrderOthers();
-        a.addBeforeOrder("c");
-        b.addBeforeOrderOthers();
-        d.addAfterOrderOthers();
-        e.addBeforeOrderOthers();
-        
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        // A number of orders are possible but the algorithm is deterministic
-        // and this order is valid. If this fails after a change to the
-        // algorithm, then check to see if the new order is also valid.
-        assertEquals(b,iter.next());
-        assertEquals(e,iter.next());
-        assertEquals(f,iter.next());
-        assertEquals(a,iter.next());
-        assertEquals(c,iter.next());
-        assertEquals(d,iter.next());
-    }
-    
-    public void testOrderWebFragmentsRelative3() {
-        // Third example from spec
-        a.addAfterOrder("b");
-        c.addBeforeOrderOthers();
-        fragments.remove("e");
-        fragments.remove("f");
-
-        Set<WebXml> ordered = WebXml.orderWebFragments(app, fragments);
-        
-        Iterator<WebXml> iter = ordered.iterator();
-        // A number of orders are possible but the algorithm is deterministic
-        // and this order is valid. If this fails after a change to the
-        // algorithm, then check to see if the new order is also valid.
-        assertEquals(c,iter.next());
-        assertEquals(d,iter.next());
-        assertEquals(b,iter.next());
-        assertEquals(a,iter.next());
-    }
-    
-    public void testOrderWebFragmentsrelativeCircular() {
-        a.addBeforeOrder("b");
-        b.addBeforeOrder("a");
-
-        Exception exception = null;
-        
-        try {
-            WebXml.orderWebFragments(app, fragments);
-        } catch (Exception e1) {
-            exception = e1;
-        }
-        
-        assertTrue(exception instanceof IllegalArgumentException);
-    }
-}