/* * 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.tomcat.util.descriptor.web; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; 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.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.ServletContext; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; import javax.servlet.descriptor.JspPropertyGroupDescriptor; import javax.servlet.descriptor.TaglibDescriptor; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.buf.UDecoder; import org.apache.tomcat.util.descriptor.XmlIdentifiers; import org.apache.tomcat.util.digester.DocumentProperties; 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 extends XmlEncodingBase implements DocumentProperties.Charset { protected static final String ORDER_OTHERS = "org.apache.catalina.order.others"; private static final StringManager sm = StringManager.getManager(Constants.PACKAGE_NAME); private static final Log log = LogFactory.getLog(WebXml.class); /** * Global defaults are overridable but Servlets and Servlet mappings need to * be unique. Duplicates normally trigger an error. This flag indicates if * newly added Servlet elements are marked as overridable. */ private boolean overridable = false; public boolean isOverridable() { return overridable; } public void setOverridable(boolean overridable) { this.overridable = overridable; } /** * web.xml only elements * Absolute Ordering */ private Set<String> absoluteOrdering = null; public void createAbsoluteOrdering() { if (absoluteOrdering == null) { absoluteOrdering = new LinkedHashSet<>(); } } public void addAbsoluteOrdering(String fragmentName) { createAbsoluteOrdering(); absoluteOrdering.add(fragmentName); } public void addAbsoluteOrderingOthers() { createAbsoluteOrdering(); absoluteOrdering.add(ORDER_OTHERS); } public Set<String> getAbsoluteOrdering() { return absoluteOrdering; } /** * web-fragment.xml only elements * Relative ordering */ private final Set<String> after = new LinkedHashSet<>(); public void addAfterOrdering(String fragmentName) { after.add(fragmentName); } public void addAfterOrderingOthers() { if (before.contains(ORDER_OTHERS)) { throw new IllegalArgumentException(sm.getString( "webXml.multipleOther")); } after.add(ORDER_OTHERS); } public Set<String> getAfterOrdering() { return after; } private final Set<String> before = new LinkedHashSet<>(); public void addBeforeOrdering(String fragmentName) { before.add(fragmentName); } public void addBeforeOrderingOthers() { if (after.contains(ORDER_OTHERS)) { throw new IllegalArgumentException(sm.getString( "webXml.multipleOther")); } before.add(ORDER_OTHERS); } public Set<String> getBeforeOrdering() { return before; } // Common elements and attributes // Required attribute of web-app element public String getVersion() { StringBuilder sb = new StringBuilder(3); sb.append(majorVersion); sb.append('.'); sb.append(minorVersion); return sb.toString(); } /** * Set the version for this web.xml file * @param version Values of <code>null</code> will be ignored */ public void setVersion(String version) { if (version == null) { return; } switch (version) { case "2.4": majorVersion = 2; minorVersion = 4; break; case "2.5": majorVersion = 2; minorVersion = 5; break; case "3.0": majorVersion = 3; minorVersion = 0; break; case "3.1": majorVersion = 3; minorVersion = 1; break; case "4.0": majorVersion = 4; minorVersion = 0; break; default: log.warn(sm.getString("webXml.version.unknown", version)); } } // Optional publicId attribute private String publicId = null; public String getPublicId() { return publicId; } public void setPublicId(String publicId) { // Update major and minor version if (publicId == null) { return; } switch (publicId) { case XmlIdentifiers.WEB_22_PUBLIC: majorVersion = 2; minorVersion = 2; this.publicId = publicId; break; case XmlIdentifiers.WEB_23_PUBLIC: majorVersion = 2; minorVersion = 3; this.publicId = publicId; break; default: log.warn(sm.getString("webXml.unrecognisedPublicId", publicId)); break; } } // 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; } } // Derived major and minor version attributes // Default to 4.0 until we know otherwise private int majorVersion = 4; private int minorVersion = 0; public int getMajorVersion() { return majorVersion; } public int getMinorVersion() { return minorVersion; } // 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; } // deny-uncovered-http-methods private boolean denyUncoveredHttpMethods = false; public boolean getDenyUncoveredHttpMethods() { return denyUncoveredHttpMethods; } public void setDenyUncoveredHttpMethods(boolean denyUncoveredHttpMethods) { this.denyUncoveredHttpMethods = denyUncoveredHttpMethods; } // context-param // TODO: description (multiple with language) is ignored private final Map<String,String> contextParams = new HashMap<>(); 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 final Map<String,FilterDef> filters = new LinkedHashMap<>(); 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 final Set<FilterMap> filterMaps = new LinkedHashSet<>(); private final Set<String> filterMappingNames = new HashSet<>(); public void addFilterMapping(FilterMap filterMap) { filterMaps.add(filterMap); filterMappingNames.add(filterMap.getFilterName()); } 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 final Set<String> listeners = new LinkedHashSet<>(); 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 final Map<String,ServletDef> servlets = new HashMap<>(); public void addServlet(ServletDef servletDef) { servlets.put(servletDef.getServletName(), servletDef); if (overridable) { servletDef.setOverridable(overridable); } } public Map<String,ServletDef> getServlets() { return servlets; } // servlet-mapping // Note: URLPatterns from web.xml may be URL encoded // (http://svn.apache.org/r285186) private final Map<String,String> servletMappings = new HashMap<>(); private final Set<String> servletMappingNames = new HashSet<>(); public void addServletMapping(String urlPattern, String servletName) { addServletMappingDecoded(UDecoder.URLDecode(urlPattern, getCharset()), servletName); } public void addServletMappingDecoded(String urlPattern, String servletName) { String oldServletName = servletMappings.put(urlPattern, servletName); if (oldServletName != null) { // Duplicate mapping. As per clarification from the Servlet EG, // deployment should fail. throw new IllegalArgumentException(sm.getString( "webXml.duplicateServletMapping", oldServletName, servletName, urlPattern)); } servletMappingNames.add(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 final Map<String,String> mimeMappings = new HashMap<>(); public void addMimeMapping(String extension, String mimeType) { mimeMappings.put(extension, mimeType); } public Map<String,String> getMimeMappings() { return mimeMappings; } // welcome-file-list merge control private boolean replaceWelcomeFiles = false; private boolean alwaysAddWelcomeFiles = true; /** * When merging/parsing web.xml files into this web.xml should the current * set be completely replaced? * @param replaceWelcomeFiles <code>true</code> to replace welcome files * rather than add to the list */ public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) { this.replaceWelcomeFiles = replaceWelcomeFiles; } /** * When merging from this web.xml, should the welcome files be added to the * target web.xml even if it already contains welcome file definitions. * @param alwaysAddWelcomeFiles <code>true</code> to add welcome files */ public void setAlwaysAddWelcomeFiles(boolean alwaysAddWelcomeFiles) { this.alwaysAddWelcomeFiles = alwaysAddWelcomeFiles; } // welcome-file-list private final Set<String> welcomeFiles = new LinkedHashSet<>(); public void addWelcomeFile(String welcomeFile) { if (replaceWelcomeFiles) { welcomeFiles.clear(); replaceWelcomeFiles = false; } welcomeFiles.add(welcomeFile); } public Set<String> getWelcomeFiles() { return welcomeFiles; } // error-page private final Map<String,ErrorPage> errorPages = new HashMap<>(); 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 final Map<String,String> taglibs = new HashMap<>(); 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 final Set<JspPropertyGroup> jspPropertyGroups = new LinkedHashSet<>(); public void addJspPropertyGroup(JspPropertyGroup propertyGroup) { propertyGroup.setCharset(getCharset()); 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 final Set<SecurityConstraint> securityConstraints = new HashSet<>(); public void addSecurityConstraint(SecurityConstraint securityConstraint) { securityConstraint.setCharset(getCharset()); 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 final Set<String> securityRoles = new HashSet<>(); 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 final Map<String,ContextEnvironment> envEntries = new HashMap<>(); 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 final Map<String,ContextEjb> ejbRefs = new HashMap<>(); 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 final Map<String,ContextLocalEjb> ejbLocalRefs = new HashMap<>(); 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 final Map<String,ContextService> serviceRefs = new HashMap<>(); 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 final Map<String,ContextResource> resourceRefs = new HashMap<>(); 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 final Map<String,ContextResourceEnvRef> resourceEnvRefs = new HashMap<>(); 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 final Map<String,MessageDestinationRef> messageDestinationRefs = new HashMap<>(); 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 final Map<String,MessageDestination> messageDestinations = new HashMap<>(); 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-encoding-mapping-list private final Map<String,String> localeEncodingMappings = new HashMap<>(); public void addLocaleEncodingMapping(String locale, String encoding) { localeEncodingMappings.put(locale, encoding); } public Map<String,String> getLocaleEncodingMappings() { return localeEncodingMappings; } // post-construct elements private Map<String, String> postConstructMethods = new HashMap<>(); public void addPostConstructMethods(String clazz, String method) { if (!postConstructMethods.containsKey(clazz)) { postConstructMethods.put(clazz, method); } } public Map<String, String> getPostConstructMethods() { return postConstructMethods; } // pre-destroy elements private Map<String, String> preDestroyMethods = new HashMap<>(); public void addPreDestroyMethods(String clazz, String method) { if (!preDestroyMethods.containsKey(clazz)) { preDestroyMethods.put(clazz, method); } } public Map<String, String> getPreDestroyMethods() { return preDestroyMethods; } public JspConfigDescriptor getJspConfigDescriptor() { if (jspPropertyGroups.isEmpty() && taglibs.isEmpty()) { return null; } Collection<JspPropertyGroupDescriptor> descriptors = new ArrayList<>(jspPropertyGroups.size()); for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) { JspPropertyGroupDescriptor descriptor = new JspPropertyGroupDescriptorImpl(jspPropertyGroup); descriptors.add(descriptor); } Collection<TaglibDescriptor> tlds = new HashSet<>(taglibs.size()); for (Entry<String, String> entry : taglibs.entrySet()) { TaglibDescriptor descriptor = new TaglibDescriptorImpl( entry.getValue(), entry.getKey()); tlds.add(descriptor); } return new JspConfigDescriptorImpl(descriptors, tlds); } private String requestCharacterEncoding; public String getRequestCharacterEncoding() { return requestCharacterEncoding; } public void setRequestCharacterEncoding(String requestCharacterEncoding) { if (requestCharacterEncoding != null) { try { B2CConverter.getCharset(requestCharacterEncoding); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } this.requestCharacterEncoding = requestCharacterEncoding; } private String responseCharacterEncoding; public String getResponseCharacterEncoding() { return responseCharacterEncoding; } public void setResponseCharacterEncoding(String responseCharacterEncoding) { if (responseCharacterEncoding != null) { try { B2CConverter.getCharset(responseCharacterEncoding); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } this.responseCharacterEncoding = responseCharacterEncoding; } // 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; } // Name of jar file private String jarName = null; public void setJarName(String jarName) { this.jarName = jarName; } public String getJarName() { return jarName; } // Is this JAR part of the application or is it a container JAR? Assume it // is. private boolean webappJar = true; public void setWebappJar(boolean webappJar) { this.webappJar = webappJar; } public boolean getWebappJar() { return webappJar; } // Does this web application delegate first for class loading? private boolean delegate = false; public boolean getDelegate() { return delegate; } public void setDelegate(boolean delegate) { this.delegate = delegate; } @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 // NOTE - Elements need to be written in the order defined in the 2.3 // DTD else validation of the merged web.xml will fail // NOTE - Some elements need to be skipped based on the version of the // specification being used. Version is validated and starts at // 2.2. The version tests used in this method take advantage of // this. // Declaration sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); // Root element if (publicId != null) { sb.append("<!DOCTYPE web-app PUBLIC\n"); sb.append(" \""); sb.append(publicId); sb.append("\"\n"); sb.append(" \""); if (XmlIdentifiers.WEB_22_PUBLIC.equals(publicId)) { sb.append(XmlIdentifiers.WEB_22_SYSTEM); } else { sb.append(XmlIdentifiers.WEB_23_SYSTEM); } sb.append("\">\n"); sb.append("<web-app>"); } else { String javaeeNamespace = null; String webXmlSchemaLocation = null; String version = getVersion(); if ("2.4".equals(version)) { javaeeNamespace = XmlIdentifiers.JAVAEE_1_4_NS; webXmlSchemaLocation = XmlIdentifiers.WEB_24_XSD; } else if ("2.5".equals(version)) { javaeeNamespace = XmlIdentifiers.JAVAEE_5_NS; webXmlSchemaLocation = XmlIdentifiers.WEB_25_XSD; } else if ("3.0".equals(version)) { javaeeNamespace = XmlIdentifiers.JAVAEE_6_NS; webXmlSchemaLocation = XmlIdentifiers.WEB_30_XSD; } else if ("3.1".equals(version)) { javaeeNamespace = XmlIdentifiers.JAVAEE_7_NS; webXmlSchemaLocation = XmlIdentifiers.WEB_31_XSD; } else if ("4.0".equals(version)) { javaeeNamespace = XmlIdentifiers.JAVAEE_8_NS; webXmlSchemaLocation = XmlIdentifiers.WEB_40_XSD; } sb.append("<web-app xmlns=\""); sb.append(javaeeNamespace); sb.append("\"\n"); sb.append(" xmlns:xsi="); sb.append("\"http://www.w3.org/2001/XMLSchema-instance\"\n"); sb.append(" xsi:schemaLocation=\""); sb.append(javaeeNamespace); sb.append(" "); sb.append(webXmlSchemaLocation); sb.append("\"\n"); sb.append(" version=\""); sb.append(getVersion()); sb.append("\""); if ("2.4".equals(version)) { sb.append(">\n\n"); } else { sb.append("\n 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-value", entry.getValue()); sb.append(" </context-param>\n"); } sb.append('\n'); // Filters were introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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()); // Async support was introduced for Servlet 3.0 onwards if (getMajorVersion() != 2) { 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", encodeUrl(urlPattern)); } } // dispatcher was added in Servlet 2.4 if (getMajorVersion() > 2 || getMinorVersion() > 3) { for (String dispatcher : filterMap.getDispatcherNames()) { if (getMajorVersion() == 2 && DispatcherType.ASYNC.name().equals(dispatcher)) { continue; } appendElement(sb, INDENT4, "dispatcher", dispatcher); } } sb.append(" </filter-mapping>\n"); } sb.append('\n'); } // Listeners were introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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()); // Async support was introduced for Servlet 3.0 onwards if (getMajorVersion() != 2) { appendElement(sb, INDENT4, "async-supported", servletDef.getAsyncSupported()); } // servlet/run-as was introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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"); } // multipart-config was added in Servlet 3.0 if (getMajorVersion() != 2) { 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", encodeUrl(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()); if (majorVersion >= 3) { 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()) { String exceptionType = errorPage.getExceptionType(); int errorCode = errorPage.getErrorCode(); if (exceptionType == null && errorCode == 0 && getMajorVersion() == 2) { // Default error pages are only supported from 3.0 onwards continue; } sb.append(" <error-page>\n"); if (errorPage.getExceptionType() != null) { appendElement(sb, INDENT4, "exception-type", exceptionType); } else if (errorPage.getErrorCode() > 0) { appendElement(sb, INDENT4, "error-code", Integer.toString(errorCode)); } appendElement(sb, INDENT4, "location", errorPage.getLocation()); sb.append(" </error-page>\n"); } sb.append('\n'); // jsp-config was added in Servlet 2.4. Prior to that, tag-libs was used // directly and jsp-property-group did not exist if (taglibs.size() > 0 || jspPropertyGroups.size() > 0) { if (getMajorVersion() > 2 || getMinorVersion() > 3) { 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"); } if (getMajorVersion() > 2 || getMinorVersion() > 3) { for (JspPropertyGroup jpg : jspPropertyGroups) { sb.append(" <jsp-property-group>\n"); for (String urlPattern : jpg.getUrlPatterns()) { appendElement(sb, INDENT6, "url-pattern", encodeUrl(urlPattern)); } appendElement(sb, INDENT6, "el-ignored", jpg.getElIgnored()); appendElement(sb, INDENT6, "page-encoding", jpg.getPageEncoding()); appendElement(sb, INDENT6, "scripting-invalid", jpg.getScriptingInvalid()); appendElement(sb, INDENT6, "is-xml", jpg.getIsXml()); 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, "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"); } } // resource-env-ref was introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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 (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()); // resource-ref/res-sharing-scope was introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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 (SecurityConstraint constraint : securityConstraints) { sb.append(" <security-constraint>\n"); // security-constraint/display-name was introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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", encodeUrl(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'); // ejb-local-ref was introduced in Servlet 2.3 if (getMajorVersion() > 2 || getMinorVersion() > 2) { 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'); } // service-ref was introduced in Servlet 2.4 if (getMajorVersion() > 2 || getMinorVersion() > 3) { 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'); } if (!postConstructMethods.isEmpty()) { for (Entry<String, String> entry : postConstructMethods .entrySet()) { sb.append(" <post-construct>\n"); appendElement(sb, INDENT4, "lifecycle-callback-class", entry.getKey()); appendElement(sb, INDENT4, "lifecycle-callback-method", entry.getValue()); sb.append(" </post-construct>\n"); } sb.append('\n'); } if (!preDestroyMethods.isEmpty()) { for (Entry<String, String> entry : preDestroyMethods .entrySet()) { sb.append(" <pre-destroy>\n"); appendElement(sb, INDENT4, "lifecycle-callback-class", entry.getKey()); appendElement(sb, INDENT4, "lifecycle-callback-method", entry.getValue()); sb.append(" </pre-destroy>\n"); } sb.append('\n'); } // message-destination-ref, message-destination were introduced in // Servlet 2.4 if (getMajorVersion() > 2 || getMinorVersion() > 3) { 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'); } // locale-encoding-mapping-list was introduced in Servlet 2.4 if (getMajorVersion() > 2 || getMinorVersion() > 3) { 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("\n"); } } // deny-uncovered-http-methods was introduced in Servlet 3.1 if (getMajorVersion() > 3 || (getMajorVersion() == 3 && getMinorVersion() > 0)) { if (denyUncoveredHttpMethods) { sb.append(" <deny-uncovered-http-methods/>"); sb.append("\n"); } } // request-encoding and response-encoding was introduced in Servlet 4.0 if (getMajorVersion() >= 4) { appendElement(sb, INDENT2, "request-character-encoding", requestCharacterEncoding); appendElement(sb, INDENT2, "response-character-encoding", responseCharacterEncoding); } sb.append("</web-app>"); return sb.toString(); } private String encodeUrl(String input) { try { return URLEncoder.encode(input, "UTF-8"); } catch (UnsupportedEncodingException e) { // Impossible. UTF-8 is a required character set return null; } } private static void appendElement(StringBuilder sb, String indent, String elementName, String value) { if (value == null) { return; } if (value.length() == 0) { sb.append(indent); sb.append('<'); sb.append(elementName); sb.append("/>\n"); } else { 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("<"); } else if (c == '>') { sb.append(">"); } else if (c == '\'') { sb.append("'"); } else if (c == '&') { sb.append("&"); } else if (c == '"') { sb.append("""); } else { sb.append(c); } } return sb.toString(); } /** * 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(); 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(); } // Note: Not permitted in fragments but we also use fragments for // per-Host and global defaults so they may appear there if (!denyUncoveredHttpMethods) { for (WebXml fragment : fragments) { if (fragment.getDenyUncoveredHttpMethods()) { denyUncoveredHttpMethods = true; break; } } } if (requestCharacterEncoding == null) { for (WebXml fragment : fragments) { if (fragment.getRequestCharacterEncoding() != null) { requestCharacterEncoding = fragment.getRequestCharacterEncoding(); } } } if (responseCharacterEncoding == null) { for (WebXml fragment : fragments) { if (fragment.getResponseCharacterEncoding() != null) { responseCharacterEncoding = fragment.getResponseCharacterEncoding(); } } } if (distributable) { for (WebXml fragment : fragments) { if (!fragment.isDistributable()) { distributable = false; break; } } } for (WebXml fragment : fragments) { if (!mergeResourceMap(fragment.getEjbLocalRefs(), ejbLocalRefs, temp.getEjbLocalRefs(), fragment)) { return false; } } ejbLocalRefs.putAll(temp.getEjbLocalRefs()); for (WebXml fragment : fragments) { if (!mergeResourceMap(fragment.getEjbRefs(), ejbRefs, temp.getEjbRefs(), fragment)) { return false; } } ejbRefs.putAll(temp.getEjbRefs()); for (WebXml fragment : fragments) { if (!mergeResourceMap(fragment.getEnvEntries(), envEntries, temp.getEnvEntries(), fragment)) { return false; } } envEntries.putAll(temp.getEnvEntries()); for (WebXml fragment : fragments) { if (!mergeMap(fragment.getErrorPages(), errorPages, temp.getErrorPages(), fragment, "Error Page")) { return false; } } errorPages.putAll(temp.getErrorPages()); // As per 'clarification' from the Servlet EG, filter definitions in the // main web.xml override those in fragments and those in fragments // override those in annotations List<FilterMap> filterMapsToAdd = new ArrayList<>(); for (WebXml fragment : fragments) { for (FilterMap filterMap : fragment.getFilterMappings()) { if (!filterMappingNames.contains(filterMap.getFilterName())) { filterMapsToAdd.add(filterMap); } } } for (FilterMap filterMap : filterMapsToAdd) { // 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.getLocaleEncodingMappings(), localeEncodingMappings, temp.getLocaleEncodingMappings(), fragment, "Locale Encoding Mapping")) { return false; } } localeEncodingMappings.putAll(temp.getLocaleEncodingMappings()); 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(), fragment)) { return false; } } messageDestinationRefs.putAll(temp.getMessageDestinationRefs()); for (WebXml fragment : fragments) { if (!mergeResourceMap(fragment.getMessageDestinations(), messageDestinations, temp.getMessageDestinations(), fragment)) { return false; } } messageDestinations.putAll(temp.getMessageDestinations()); 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(), fragment)) { return false; } } resourceEnvRefs.putAll(temp.getResourceEnvRefs()); for (WebXml fragment : fragments) { if (!mergeResourceMap(fragment.getResourceRefs(), resourceRefs, temp.getResourceRefs(), fragment)) { return false; } } resourceRefs.putAll(temp.getResourceRefs()); 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(), fragment)) { return false; } } serviceRefs.putAll(temp.getServiceRefs()); // As per 'clarification' from the Servlet EG, servlet definitions and // mappings in the main web.xml override those in fragments and those in // fragments override those in annotations // Skip servlet definitions and mappings from fragments that are // defined in web.xml List<Map.Entry<String,String>> servletMappingsToAdd = new ArrayList<>(); for (WebXml fragment : fragments) { for (Map.Entry<String,String> servletMap : fragment.getServletMappings().entrySet()) { if (!servletMappingNames.contains(servletMap.getValue()) && !servletMappings.containsKey(servletMap.getKey())) { servletMappingsToAdd.add(servletMap); } } } // Add fragment mappings for (Map.Entry<String,String> mapping : servletMappingsToAdd) { addServletMappingDecoded(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) { if (fragment.alwaysAddWelcomeFiles || welcomeFiles.size() == 0) { for (String welcomeFile : fragment.getWelcomeFiles()) { addWelcomeFile(welcomeFile); } } } if (postConstructMethods.isEmpty()) { for (WebXml fragment : fragments) { if (!mergeLifecycleCallback(fragment.getPostConstructMethods(), temp.getPostConstructMethods(), fragment, "Post Construct Methods")) { return false; } } postConstructMethods.putAll(temp.getPostConstructMethods()); } if (preDestroyMethods.isEmpty()) { for (WebXml fragment : fragments) { if (!mergeLifecycleCallback(fragment.getPreDestroyMethods(), temp.getPreDestroyMethods(), fragment, "Pre Destroy Methods")) { return false; } } preDestroyMethods.putAll(temp.getPreDestroyMethods()); } return true; } private static <T extends ResourceBase> boolean mergeResourceMap( Map<String, T> fragmentResources, Map<String, T> mainResources, Map<String, T> tempResources, WebXml fragment) { for (T resource : fragmentResources.values()) { String resourceName = resource.getName(); if (mainResources.containsKey(resourceName)) { mainResources.get(resourceName).getInjectionTargets().addAll( resource.getInjectionTargets()); } else { // Not defined in main web.xml T existingResource = tempResources.get(resourceName); if (existingResource != null) { if (!existingResource.equals(resource)) { log.error(sm.getString( "webXml.mergeConflictResource", resourceName, fragment.getName(), fragment.getURL())); return false; } } else { 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 (Entry<String, T> entry : fragmentMap.entrySet()) { final String key = entry.getKey(); if (!mainMap.containsKey(key)) { // Not defined in main web.xml T value = entry.getValue(); if (tempMap.containsKey(key)) { if (value != null && !value.equals( tempMap.get(key))) { log.error(sm.getString( "webXml.mergeConflictString", mapName, key, fragment.getName(), fragment.getURL())); return false; } } else { tempMap.put(key, value); } } } return true; } 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) { if (src.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) { if (src.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) { if (src.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.getMaxFileSize() != 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; } private static boolean mergeLifecycleCallback( Map<String, String> fragmentMap, Map<String, String> tempMap, WebXml fragment, String mapName) { for (Entry<String, String> entry : fragmentMap.entrySet()) { final String key = entry.getKey(); final String value = entry.getValue(); if (tempMap.containsKey(key)) { if (value != null && !value.equals(tempMap.get(key))) { log.error(sm.getString("webXml.mergeConflictString", mapName, key, fragment.getName(), fragment.getURL())); return false; } } else { tempMap.put(key, value); } } return true; } /** * Generates the sub-set of the web-fragment.xml files to be processed in * the order that the fragments must be processed as per the rules in the * Servlet spec. * * @param application The application web.xml file * @param fragments The map of fragment names to web fragments * @param servletContext The servlet context the fragments are associated * with * @return Ordered list of web-fragment.xml files to process */ public static Set<WebXml> orderWebFragments(WebXml application, Map<String,WebXml> fragments, ServletContext servletContext) { Set<WebXml> orderedFragments = new LinkedHashSet<>(); boolean absoluteOrdering = (application.getAbsoluteOrdering() != null); boolean orderingPresent = false; if (absoluteOrdering) { orderingPresent = true; // 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 (Entry<String, WebXml> entry : fragments.entrySet()) { if (!requestedOrder.contains(entry.getKey())) { WebXml fragment = entry.getValue(); if (fragment != null) { orderedFragments.add(fragment); } } } } else { WebXml fragment = fragments.get(requestedName); if (fragment != null) { orderedFragments.add(fragment); } else { log.warn(sm.getString("webXml.wrongFragmentName",requestedName)); } } } } else { // Stage 1. Make all dependencies bi-directional - this makes the // next stage simpler. for (WebXml fragment : fragments.values()) { Iterator<String> before = fragment.getBeforeOrdering().iterator(); while (before.hasNext()) { orderingPresent = true; String beforeEntry = before.next(); if (!beforeEntry.equals(ORDER_OTHERS)) { WebXml beforeFragment = fragments.get(beforeEntry); if (beforeFragment == null) { before.remove(); } else { beforeFragment.addAfterOrdering(fragment.getName()); } } } Iterator<String> after = fragment.getAfterOrdering().iterator(); while (after.hasNext()) { orderingPresent = true; String afterEntry = after.next(); if (!afterEntry.equals(ORDER_OTHERS)) { WebXml afterFragment = fragments.get(afterEntry); if (afterFragment == null) { after.remove(); } else { afterFragment.addBeforeOrdering(fragment.getName()); } } } } // Stage 2. Make all fragments that are implicitly before/after // others explicitly so. This is iterative so the next // stage doesn't have to be. for (WebXml fragment : fragments.values()) { if (fragment.getBeforeOrdering().contains(ORDER_OTHERS)) { makeBeforeOthersExplicit(fragment.getAfterOrdering(), fragments); } if (fragment.getAfterOrdering().contains(ORDER_OTHERS)) { makeAfterOthersExplicit(fragment.getBeforeOrdering(), fragments); } } // Stage 3. Separate into three groups Set<WebXml> beforeSet = new HashSet<>(); Set<WebXml> othersSet = new HashSet<>(); Set<WebXml> afterSet = new HashSet<>(); for (WebXml fragment : fragments.values()) { if (fragment.getBeforeOrdering().contains(ORDER_OTHERS)) { beforeSet.add(fragment); fragment.getBeforeOrdering().remove(ORDER_OTHERS); } else if (fragment.getAfterOrdering().contains(ORDER_OTHERS)) { afterSet.add(fragment); fragment.getAfterOrdering().remove(ORDER_OTHERS); } else { othersSet.add(fragment); } } // Stage 4. Decouple the groups so the ordering requirements for // each fragment in the group only refer to other fragments // in the group. Ordering requirements outside the group // will be handled by processing the groups in order. // Note: Only after ordering requirements are considered. // This is OK because of the processing in stage 1. decoupleOtherGroups(beforeSet); decoupleOtherGroups(othersSet); decoupleOtherGroups(afterSet); // Stage 5. Order each group // Note: Only after ordering requirements are considered. // This is OK because of the processing in stage 1. orderFragments(orderedFragments, beforeSet); orderFragments(orderedFragments, othersSet); orderFragments(orderedFragments, afterSet); } // Container fragments are always included Set<WebXml> containerFragments = new LinkedHashSet<>(); // Find all the container fragments and remove any present from the // ordered list for (WebXml fragment : fragments.values()) { if (!fragment.getWebappJar()) { containerFragments.add(fragment); orderedFragments.remove(fragment); } } // Avoid NPE when unit testing if (servletContext != null) { // Publish the ordered fragments. The app does not need to know // about container fragments List<String> orderedJarFileNames = null; if (orderingPresent) { orderedJarFileNames = new ArrayList<>(); for (WebXml fragment: orderedFragments) { orderedJarFileNames.add(fragment.getJarName()); } } servletContext.setAttribute(ServletContext.ORDERED_LIBS, orderedJarFileNames); } // The remainder of the processing needs to know about container // fragments if (containerFragments.size() > 0) { Set<WebXml> result = new LinkedHashSet<>(); if (containerFragments.iterator().next().getDelegate()) { result.addAll(containerFragments); result.addAll(orderedFragments); } else { result.addAll(orderedFragments); result.addAll(containerFragments); } return result; } else { return orderedFragments; } } private static void decoupleOtherGroups(Set<WebXml> group) { Set<String> names = new HashSet<>(); for (WebXml fragment : group) { names.add(fragment.getName()); } for (WebXml fragment : group) { Iterator<String> after = fragment.getAfterOrdering().iterator(); while (after.hasNext()) { String entry = after.next(); if (!names.contains(entry)) { after.remove(); } } } } private static void orderFragments(Set<WebXml> orderedFragments, Set<WebXml> unordered) { Set<WebXml> addedThisRound = new HashSet<>(); Set<WebXml> addedLastRound = new HashSet<>(); while (unordered.size() > 0) { Iterator<WebXml> source = unordered.iterator(); while (source.hasNext()) { WebXml fragment = source.next(); for (WebXml toRemove : addedLastRound) { fragment.getAfterOrdering().remove(toRemove.getName()); } if (fragment.getAfterOrdering().isEmpty()) { addedThisRound.add(fragment); orderedFragments.add(fragment); source.remove(); } } if (addedThisRound.size() == 0) { // Circular throw new IllegalArgumentException( sm.getString("webXml.mergeConflictOrder")); } addedLastRound.clear(); addedLastRound.addAll(addedThisRound); addedThisRound.clear(); } } private static void makeBeforeOthersExplicit(Set<String> beforeOrdering, Map<String, WebXml> fragments) { for (String before : beforeOrdering) { if (!before.equals(ORDER_OTHERS)) { WebXml webXml = fragments.get(before); if (!webXml.getBeforeOrdering().contains(ORDER_OTHERS)) { webXml.addBeforeOrderingOthers(); makeBeforeOthersExplicit(webXml.getAfterOrdering(), fragments); } } } } private static void makeAfterOthersExplicit(Set<String> afterOrdering, Map<String, WebXml> fragments) { for (String after : afterOrdering) { if (!after.equals(ORDER_OTHERS)) { WebXml webXml = fragments.get(after); if (!webXml.getAfterOrdering().contains(ORDER_OTHERS)) { webXml.addAfterOrderingOthers(); makeAfterOthersExplicit(webXml.getBeforeOrdering(), fragments); } } } } }