1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.tomcat.util.descriptor.web;
18
19 import java.io.UnsupportedEncodingException;
20 import java.net.URL;
21 import java.net.URLEncoder;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.EnumSet;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Set;
34
35 import javax.servlet.DispatcherType;
36 import javax.servlet.ServletContext;
37 import javax.servlet.SessionTrackingMode;
38 import javax.servlet.descriptor.JspConfigDescriptor;
39 import javax.servlet.descriptor.JspPropertyGroupDescriptor;
40 import javax.servlet.descriptor.TaglibDescriptor;
41
42 import org.apache.juli.logging.Log;
43 import org.apache.juli.logging.LogFactory;
44 import org.apache.tomcat.util.buf.B2CConverter;
45 import org.apache.tomcat.util.buf.UDecoder;
46 import org.apache.tomcat.util.descriptor.XmlIdentifiers;
47 import org.apache.tomcat.util.digester.DocumentProperties;
48 import org.apache.tomcat.util.res.StringManager;
49 import org.apache.tomcat.util.security.Escape;
50
51 /**
52  * Representation of common elements of web.xml and web-fragment.xml. Provides
53  * a repository for parsed data before the elements are merged.
54  * Validation is spread between multiple classes:
55  * The digester checks for structural correctness (eg single login-config)
56  * This class checks for invalid duplicates (eg filter/servlet names)
57  * StandardContext will check validity of values (eg URL formats etc)
58  */

59 public class WebXml extends XmlEncodingBase implements DocumentProperties.Charset {
60
61     protected static final String ORDER_OTHERS =
62         "org.apache.catalina.order.others";
63
64     private static final StringManager sm =
65         StringManager.getManager(Constants.PACKAGE_NAME);
66
67     private final Log log = LogFactory.getLog(WebXml.class); // must not be static
68
69     /**
70      * Global defaults are overridable but Servlets and Servlet mappings need to
71      * be unique. Duplicates normally trigger an error. This flag indicates if
72      * newly added Servlet elements are marked as overridable.
73      */

74     private boolean overridable = false;
75     public boolean isOverridable() {
76         return overridable;
77     }
78     public void setOverridable(boolean overridable) {
79         this.overridable = overridable;
80     }
81
82     /*
83      * Ideally, fragment names will be unique. If they are not, Tomcat needs
84      * to know as the action that the specification requires (see 8.2.2 1.e and
85      * 2.c) varies depending on the ordering method used.
86      */

87     private boolean duplicated = false;
88     public boolean isDuplicated() {
89         return duplicated;
90     }
91     public void setDuplicated(boolean duplicated) {
92         this.duplicated = duplicated;
93     }
94
95     /**
96      * web.xml only elements
97      * Absolute Ordering
98      */

99     private Set<String> absoluteOrdering = null;
100     public void createAbsoluteOrdering() {
101         if (absoluteOrdering == null) {
102             absoluteOrdering = new LinkedHashSet<>();
103         }
104     }
105     public void addAbsoluteOrdering(String fragmentName) {
106         createAbsoluteOrdering();
107         absoluteOrdering.add(fragmentName);
108     }
109     public void addAbsoluteOrderingOthers() {
110         createAbsoluteOrdering();
111         absoluteOrdering.add(ORDER_OTHERS);
112     }
113     public Set<String> getAbsoluteOrdering() {
114         return absoluteOrdering;
115     }
116
117     /**
118      * web-fragment.xml only elements
119      * Relative ordering
120      */

121     private final Set<String> after = new LinkedHashSet<>();
122     public void addAfterOrdering(String fragmentName) {
123         after.add(fragmentName);
124     }
125     public void addAfterOrderingOthers() {
126         if (before.contains(ORDER_OTHERS)) {
127             throw new IllegalArgumentException(sm.getString(
128                     "webXml.multipleOther"));
129         }
130         after.add(ORDER_OTHERS);
131     }
132     public Set<String> getAfterOrdering() { return after; }
133
134     private final Set<String> before = new LinkedHashSet<>();
135     public void addBeforeOrdering(String fragmentName) {
136         before.add(fragmentName);
137     }
138     public void addBeforeOrderingOthers() {
139         if (after.contains(ORDER_OTHERS)) {
140             throw new IllegalArgumentException(sm.getString(
141                     "webXml.multipleOther"));
142         }
143         before.add(ORDER_OTHERS);
144     }
145     public Set<String> getBeforeOrdering() { return before; }
146
147     // Common elements and attributes
148     // Required attribute of web-app element
149     public String getVersion() {
150         StringBuilder sb = new StringBuilder(3);
151         sb.append(majorVersion);
152         sb.append('.');
153         sb.append(minorVersion);
154         return sb.toString();
155     }
156     /**
157      * Set the version for this web.xml file
158      * @param version   Values of <code>null</code> will be ignored
159      */

160     public void setVersion(String version) {
161         if (version == null) {
162             return;
163         }
164         switch (version) {
165             case "2.4":
166                 majorVersion = 2;
167                 minorVersion = 4;
168                 break;
169             case "2.5":
170                 majorVersion = 2;
171                 minorVersion = 5;
172                 break;
173             case "3.0":
174                 majorVersion = 3;
175                 minorVersion = 0;
176                 break;
177             case "3.1":
178                 majorVersion = 3;
179                 minorVersion = 1;
180                 break;
181             case "4.0":
182                 majorVersion = 4;
183                 minorVersion = 0;
184                 break;
185             default:
186                 log.warn(sm.getString("webXml.version.unknown", version));
187         }
188     }
189
190
191
192     // Optional publicId attribute
193     private String publicId = null;
194     public String getPublicId() { return publicId; }
195     public void setPublicId(String publicId) {
196         // Update major and minor version
197         if (publicId == null) {
198             return;
199         }
200         switch (publicId) {
201             case XmlIdentifiers.WEB_22_PUBLIC:
202                 majorVersion = 2;
203                 minorVersion = 2;
204                 this.publicId = publicId;
205                 break;
206             case XmlIdentifiers.WEB_23_PUBLIC:
207                 majorVersion = 2;
208                 minorVersion = 3;
209                 this.publicId = publicId;
210                 break;
211             default:
212                 log.warn(sm.getString("webXml.unrecognisedPublicId", publicId));
213                 break;
214         }
215     }
216
217     // Optional metadata-complete attribute
218     private boolean metadataComplete = false;
219     public boolean isMetadataComplete() { return metadataComplete; }
220     public void setMetadataComplete(boolean metadataComplete) {
221         this.metadataComplete = metadataComplete; }
222
223     // Optional name element
224     private String name = null;
225     public String getName() { return name; }
226     public void setName(String name) {
227         if (ORDER_OTHERS.equalsIgnoreCase(name)) {
228             // This is unusual. This name will be ignored. Log the fact.
229             log.warn(sm.getString("webXml.reservedName", name));
230         } else {
231             this.name = name;
232         }
233     }
234
235     // Derived major and minor version attributes
236     // Default to 4.0 until we know otherwise
237     private int majorVersion = 4;
238     private int minorVersion = 0;
239     public int getMajorVersion() { return majorVersion; }
240     public int getMinorVersion() { return minorVersion; }
241
242     // web-app elements
243     // TODO: Ignored elements:
244     // - description
245     // - icon
246
247     // display-name - TODO should support multiple with language
248     private String displayName = null;
249     public String getDisplayName() { return displayName; }
250     public void setDisplayName(String displayName) {
251         this.displayName = displayName;
252     }
253
254     // distributable
255     private boolean distributable = false;
256     public boolean isDistributable() { return distributable; }
257     public void setDistributable(boolean distributable) {
258         this.distributable = distributable;
259     }
260
261     // deny-uncovered-http-methods
262     private boolean denyUncoveredHttpMethods = false;
263     public boolean getDenyUncoveredHttpMethods() {
264         return denyUncoveredHttpMethods;
265     }
266     public void setDenyUncoveredHttpMethods(boolean denyUncoveredHttpMethods) {
267         this.denyUncoveredHttpMethods = denyUncoveredHttpMethods;
268     }
269
270     // context-param
271     // TODO: description (multiple with language) is ignored
272     private final Map<String,String> contextParams = new HashMap<>();
273     public void addContextParam(String param, String value) {
274         contextParams.put(param, value);
275     }
276     public Map<String,String> getContextParams() { return contextParams; }
277
278     // filter
279     // TODO: Should support multiple description elements with language
280     // TODO: Should support multiple display-name elements with language
281     // TODO: Should support multiple icon elements
282     // TODO: Description for init-param is ignored
283     private final Map<String,FilterDef> filters = new LinkedHashMap<>();
284     public void addFilter(FilterDef filter) {
285         if (filters.containsKey(filter.getFilterName())) {
286             // Filter names must be unique within a web(-fragment).xml
287             throw new IllegalArgumentException(
288                     sm.getString("webXml.duplicateFilter",
289                             filter.getFilterName()));
290         }
291         filters.put(filter.getFilterName(), filter);
292     }
293     public Map<String,FilterDef> getFilters() { return filters; }
294
295     // filter-mapping
296     private final Set<FilterMap> filterMaps = new LinkedHashSet<>();
297     private final Set<String> filterMappingNames = new HashSet<>();
298     public void addFilterMapping(FilterMap filterMap) {
299         filterMaps.add(filterMap);
300         filterMappingNames.add(filterMap.getFilterName());
301     }
302     public Set<FilterMap> getFilterMappings() { return filterMaps; }
303
304     // listener
305     // TODO: description (multiple with language) is ignored
306     // TODO: display-name (multiple with language) is ignored
307     // TODO: icon (multiple) is ignored
308     private final Set<String> listeners = new LinkedHashSet<>();
309     public void addListener(String className) {
310         listeners.add(className);
311     }
312     public Set<String> getListeners() { return listeners; }
313
314     // servlet
315     // TODO: description (multiple with language) is ignored
316     // TODO: display-name (multiple with language) is ignored
317     // TODO: icon (multiple) is ignored
318     // TODO: init-param/description (multiple with language) is ignored
319     // TODO: security-role-ref/description (multiple with language) is ignored
320     private final Map<String,ServletDef> servlets = new HashMap<>();
321     public void addServlet(ServletDef servletDef) {
322         servlets.put(servletDef.getServletName(), servletDef);
323         if (overridable) {
324             servletDef.setOverridable(overridable);
325         }
326     }
327     public Map<String,ServletDef> getServlets() { return servlets; }
328
329     // servlet-mapping
330     // Note: URLPatterns from web.xml may be URL encoded
331     //       (https://svn.apache.org/r285186)
332     private final Map<String,String> servletMappings = new HashMap<>();
333     private final Set<String> servletMappingNames = new HashSet<>();
334     public void addServletMapping(String urlPattern, String servletName) {
335         addServletMappingDecoded(UDecoder.URLDecode(urlPattern, getCharset()), servletName);
336     }
337     public void addServletMappingDecoded(String urlPattern, String servletName) {
338         String oldServletName = servletMappings.put(urlPattern, servletName);
339         if (oldServletName != null) {
340             // Duplicate mapping. As per clarification from the Servlet EG,
341             // deployment should fail.
342             throw new IllegalArgumentException(sm.getString(
343                     "webXml.duplicateServletMapping", oldServletName,
344                     servletName, urlPattern));
345         }
346         servletMappingNames.add(servletName);
347     }
348     public Map<String,String> getServletMappings() { return servletMappings; }
349
350     // session-config
351     // Digester will check there is only one of these
352     private SessionConfig sessionConfig = new SessionConfig();
353     public void setSessionConfig(SessionConfig sessionConfig) {
354         this.sessionConfig = sessionConfig;
355     }
356     public SessionConfig getSessionConfig() { return sessionConfig; }
357
358     // mime-mapping
359     private final Map<String,String> mimeMappings = new HashMap<>();
360     public void addMimeMapping(String extension, String mimeType) {
361         mimeMappings.put(extension, mimeType);
362     }
363     public Map<String,String> getMimeMappings() { return mimeMappings; }
364
365     // welcome-file-list merge control
366     private boolean replaceWelcomeFiles = false;
367     private boolean alwaysAddWelcomeFiles = true;
368     /**
369      * When merging/parsing web.xml files into this web.xml should the current
370      * set be completely replaced?
371      * @param replaceWelcomeFiles <code>true</code> to replace welcome files
372      *  rather than add to the list
373      */

374     public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) {
375         this.replaceWelcomeFiles = replaceWelcomeFiles;
376     }
377     /**
378      * When merging from this web.xml, should the welcome files be added to the
379      * target web.xml even if it already contains welcome file definitions.
380      * @param alwaysAddWelcomeFiles <code>true</code> to add welcome files
381      */

382     public void setAlwaysAddWelcomeFiles(boolean alwaysAddWelcomeFiles) {
383         this.alwaysAddWelcomeFiles = alwaysAddWelcomeFiles;
384     }
385
386     // welcome-file-list
387     private final Set<String> welcomeFiles = new LinkedHashSet<>();
388     public void addWelcomeFile(String welcomeFile) {
389         if (replaceWelcomeFiles) {
390             welcomeFiles.clear();
391             replaceWelcomeFiles = false;
392         }
393         welcomeFiles.add(welcomeFile);
394     }
395     public Set<String> getWelcomeFiles() { return welcomeFiles; }
396
397     // error-page
398     private final Map<String,ErrorPage> errorPages = new HashMap<>();
399     public void addErrorPage(ErrorPage errorPage) {
400         errorPages.put(errorPage.getName(), errorPage);
401     }
402     public Map<String,ErrorPage> getErrorPages() { return errorPages; }
403
404     // Digester will check there is only one jsp-config
405     // jsp-config/taglib or taglib (2.3 and earlier)
406     private final Map<String,String> taglibs = new HashMap<>();
407     public void addTaglib(String uri, String location) {
408         if (taglibs.containsKey(uri)) {
409             // Taglib URIs must be unique within a web(-fragment).xml
410             throw new IllegalArgumentException(
411                     sm.getString("webXml.duplicateTaglibUri", uri));
412         }
413         taglibs.put(uri, location);
414     }
415     public Map<String,String> getTaglibs() { return taglibs; }
416
417     // jsp-config/jsp-property-group
418     private final Set<JspPropertyGroup> jspPropertyGroups = new LinkedHashSet<>();
419     public void addJspPropertyGroup(JspPropertyGroup propertyGroup) {
420         propertyGroup.setCharset(getCharset());
421         jspPropertyGroups.add(propertyGroup);
422     }
423     public Set<JspPropertyGroup> getJspPropertyGroups() {
424         return jspPropertyGroups;
425     }
426
427     // security-constraint
428     // TODO: Should support multiple display-name elements with language
429     // TODO: Should support multiple description elements with language
430     private final Set<SecurityConstraint> securityConstraints = new HashSet<>();
431     public void addSecurityConstraint(SecurityConstraint securityConstraint) {
432         securityConstraint.setCharset(getCharset());
433         securityConstraints.add(securityConstraint);
434     }
435     public Set<SecurityConstraint> getSecurityConstraints() {
436         return securityConstraints;
437     }
438
439     // login-config
440     // Digester will check there is only one of these
441     private LoginConfig loginConfig = null;
442     public void setLoginConfig(LoginConfig loginConfig) {
443         this.loginConfig = loginConfig;
444     }
445     public LoginConfig getLoginConfig() { return loginConfig; }
446
447     // security-role
448     // TODO: description (multiple with language) is ignored
449     private final Set<String> securityRoles = new HashSet<>();
450     public void addSecurityRole(String securityRole) {
451         securityRoles.add(securityRole);
452     }
453     public Set<String> getSecurityRoles() { return securityRoles; }
454
455     // env-entry
456     // TODO: Should support multiple description elements with language
457     private final Map<String,ContextEnvironment> envEntries = new HashMap<>();
458     public void addEnvEntry(ContextEnvironment envEntry) {
459         if (envEntries.containsKey(envEntry.getName())) {
460             // env-entry names must be unique within a web(-fragment).xml
461             throw new IllegalArgumentException(
462                     sm.getString("webXml.duplicateEnvEntry",
463                             envEntry.getName()));
464         }
465         envEntries.put(envEntry.getName(),envEntry);
466     }
467     public Map<String,ContextEnvironment> getEnvEntries() { return envEntries; }
468
469     // ejb-ref
470     // TODO: Should support multiple description elements with language
471     private final Map<String,ContextEjb> ejbRefs = new HashMap<>();
472     public void addEjbRef(ContextEjb ejbRef) {
473         ejbRefs.put(ejbRef.getName(),ejbRef);
474     }
475     public Map<String,ContextEjb> getEjbRefs() { return ejbRefs; }
476
477     // ejb-local-ref
478     // TODO: Should support multiple description elements with language
479     private final Map<String,ContextLocalEjb> ejbLocalRefs = new HashMap<>();
480     public void addEjbLocalRef(ContextLocalEjb ejbLocalRef) {
481         ejbLocalRefs.put(ejbLocalRef.getName(),ejbLocalRef);
482     }
483     public Map<String,ContextLocalEjb> getEjbLocalRefs() {
484         return ejbLocalRefs;
485     }
486
487     // service-ref
488     // TODO: Should support multiple description elements with language
489     // TODO: Should support multiple display-names elements with language
490     // TODO: Should support multiple icon elements ???
491     private final Map<String,ContextService> serviceRefs = new HashMap<>();
492     public void addServiceRef(ContextService serviceRef) {
493         serviceRefs.put(serviceRef.getName(), serviceRef);
494     }
495     public Map<String,ContextService> getServiceRefs() { return serviceRefs; }
496
497     // resource-ref
498     // TODO: Should support multiple description elements with language
499     private final Map<String,ContextResource> resourceRefs = new HashMap<>();
500     public void addResourceRef(ContextResource resourceRef) {
501         if (resourceRefs.containsKey(resourceRef.getName())) {
502             // resource-ref names must be unique within a web(-fragment).xml
503             throw new IllegalArgumentException(
504                     sm.getString("webXml.duplicateResourceRef",
505                             resourceRef.getName()));
506         }
507         resourceRefs.put(resourceRef.getName(), resourceRef);
508     }
509     public Map<String,ContextResource> getResourceRefs() {
510         return resourceRefs;
511     }
512
513     // resource-env-ref
514     // TODO: Should support multiple description elements with language
515     private final Map<String,ContextResourceEnvRef> resourceEnvRefs = new HashMap<>();
516     public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
517         if (resourceEnvRefs.containsKey(resourceEnvRef.getName())) {
518             // resource-env-ref names must be unique within a web(-fragment).xml
519             throw new IllegalArgumentException(
520                     sm.getString("webXml.duplicateResourceEnvRef",
521                             resourceEnvRef.getName()));
522         }
523         resourceEnvRefs.put(resourceEnvRef.getName(), resourceEnvRef);
524     }
525     public Map<String,ContextResourceEnvRef> getResourceEnvRefs() {
526         return resourceEnvRefs;
527     }
528
529     // message-destination-ref
530     // TODO: Should support multiple description elements with language
531     private final Map<String,MessageDestinationRef> messageDestinationRefs =
532         new HashMap<>();
533     public void addMessageDestinationRef(
534             MessageDestinationRef messageDestinationRef) {
535         if (messageDestinationRefs.containsKey(
536                 messageDestinationRef.getName())) {
537             // message-destination-ref names must be unique within a
538             // web(-fragment).xml
539             throw new IllegalArgumentException(sm.getString(
540                     "webXml.duplicateMessageDestinationRef",
541                     messageDestinationRef.getName()));
542         }
543         messageDestinationRefs.put(messageDestinationRef.getName(),
544                 messageDestinationRef);
545     }
546     public Map<String,MessageDestinationRef> getMessageDestinationRefs() {
547         return messageDestinationRefs;
548     }
549
550     // message-destination
551     // TODO: Should support multiple description elements with language
552     // TODO: Should support multiple display-names elements with language
553     // TODO: Should support multiple icon elements ???
554     private final Map<String,MessageDestination> messageDestinations =
555             new HashMap<>();
556     public void addMessageDestination(
557             MessageDestination messageDestination) {
558         if (messageDestinations.containsKey(
559                 messageDestination.getName())) {
560             // message-destination names must be unique within a
561             // web(-fragment).xml
562             throw new IllegalArgumentException(
563                     sm.getString("webXml.duplicateMessageDestination",
564                             messageDestination.getName()));
565         }
566         messageDestinations.put(messageDestination.getName(),
567                 messageDestination);
568     }
569     public Map<String,MessageDestination> getMessageDestinations() {
570         return messageDestinations;
571     }
572
573     // locale-encoding-mapping-list
574     private final Map<String,String> localeEncodingMappings = new HashMap<>();
575     public void addLocaleEncodingMapping(String locale, String encoding) {
576         localeEncodingMappings.put(locale, encoding);
577     }
578     public Map<String,String> getLocaleEncodingMappings() {
579         return localeEncodingMappings;
580     }
581
582     // post-construct elements
583     private Map<String, String> postConstructMethods = new HashMap<>();
584     public void addPostConstructMethods(String clazz, String method) {
585         if (!postConstructMethods.containsKey(clazz)) {
586             postConstructMethods.put(clazz, method);
587         }
588     }
589     public Map<String, String> getPostConstructMethods() {
590         return postConstructMethods;
591     }
592
593     // pre-destroy elements
594     private Map<String, String> preDestroyMethods = new HashMap<>();
595     public void addPreDestroyMethods(String clazz, String method) {
596         if (!preDestroyMethods.containsKey(clazz)) {
597             preDestroyMethods.put(clazz, method);
598         }
599     }
600     public Map<String, String> getPreDestroyMethods() {
601         return preDestroyMethods;
602     }
603
604     public JspConfigDescriptor getJspConfigDescriptor() {
605         if (jspPropertyGroups.isEmpty() && taglibs.isEmpty()) {
606             return null;
607         }
608
609         Collection<JspPropertyGroupDescriptor> descriptors =
610                 new ArrayList<>(jspPropertyGroups.size());
611         for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
612             JspPropertyGroupDescriptor descriptor =
613                     new JspPropertyGroupDescriptorImpl(jspPropertyGroup);
614             descriptors.add(descriptor);
615
616         }
617
618         Collection<TaglibDescriptor> tlds = new HashSet<>(taglibs.size());
619         for (Entry<String, String> entry : taglibs.entrySet()) {
620             TaglibDescriptor descriptor = new TaglibDescriptorImpl(
621                     entry.getValue(), entry.getKey());
622             tlds.add(descriptor);
623         }
624         return new JspConfigDescriptorImpl(descriptors, tlds);
625     }
626
627     private String requestCharacterEncoding;
628     public String getRequestCharacterEncoding() {
629         return requestCharacterEncoding;
630     }
631     public void setRequestCharacterEncoding(String requestCharacterEncoding) {
632         if (requestCharacterEncoding != null) {
633             try {
634                 B2CConverter.getCharset(requestCharacterEncoding);
635             } catch (UnsupportedEncodingException e) {
636                 throw new IllegalArgumentException(e);
637             }
638         }
639         this.requestCharacterEncoding = requestCharacterEncoding;
640     }
641
642     private String responseCharacterEncoding;
643     public String getResponseCharacterEncoding() {
644         return responseCharacterEncoding;
645     }
646     public void setResponseCharacterEncoding(String responseCharacterEncoding) {
647         if (responseCharacterEncoding != null) {
648             try {
649                 B2CConverter.getCharset(responseCharacterEncoding);
650             } catch (UnsupportedEncodingException e) {
651                 throw new IllegalArgumentException(e);
652             }
653         }
654         this.responseCharacterEncoding = responseCharacterEncoding;
655     }
656
657     // Attributes not defined in web.xml or web-fragment.xml
658
659     // URL of JAR / exploded JAR for this web-fragment
660     private URL uRL = null;
661     public void setURL(URL url) { this.uRL = url; }
662     public URL getURL() { return uRL; }
663
664     // Name of jar file
665     private String jarName = null;
666     public void setJarName(String jarName) { this.jarName = jarName; }
667     public String getJarName() { return jarName; }
668
669     // Is this JAR part of the application or is it a container JAR? Assume it
670     // is.
671     private boolean webappJar = true;
672     public void setWebappJar(boolean webappJar) { this.webappJar = webappJar; }
673     public boolean getWebappJar() { return webappJar; }
674
675     // Does this web application delegate first for class loading?
676     private boolean delegate = false;
677     public boolean getDelegate() { return delegate; }
678     public void setDelegate(boolean delegate) { this.delegate = delegate; }
679
680     @Override
681     public String toString() {
682         StringBuilder buf = new StringBuilder(32);
683         buf.append("Name: ");
684         buf.append(getName());
685         buf.append(", URL: ");
686         buf.append(getURL());
687         return buf.toString();
688     }
689
690     private static final String INDENT2 = "  ";
691     private static final String INDENT4 = "    ";
692     private static final String INDENT6 = "      ";
693
694     /**
695      * Generate a web.xml in String form that matches the representation stored
696      * in this object.
697      *
698      * @return The complete contents of web.xml as a String
699      */

700     public String toXml() {
701         StringBuilder sb = new StringBuilder(2048);
702         // TODO - Various, icon, description etc elements are skipped - mainly
703         //        because they are ignored when web.xml is parsed - see above
704
705         // NOTE - Elements need to be written in the order defined in the 2.3
706         //        DTD else validation of the merged web.xml will fail
707
708         // NOTE - Some elements need to be skipped based on the version of the
709         //        specification being used. Version is validated and starts at
710         //        2.2. The version tests used in this method take advantage of
711         //        this.
712
713         // Declaration
714         sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
715
716         // Root element
717         if (publicId != null) {
718             sb.append("<!DOCTYPE web-app PUBLIC\n");
719             sb.append("  \"");
720             sb.append(publicId);
721             sb.append("\"\n");
722             sb.append("  \"");
723             if (XmlIdentifiers.WEB_22_PUBLIC.equals(publicId)) {
724                 sb.append(XmlIdentifiers.WEB_22_SYSTEM);
725             } else {
726                 sb.append(XmlIdentifiers.WEB_23_SYSTEM);
727             }
728             sb.append("\">\n");
729             sb.append("<web-app>");
730         } else {
731             String javaeeNamespace = null;
732             String webXmlSchemaLocation = null;
733             String version = getVersion();
734             if ("2.4".equals(version)) {
735                 javaeeNamespace = XmlIdentifiers.JAVAEE_1_4_NS;
736                 webXmlSchemaLocation = XmlIdentifiers.WEB_24_XSD;
737             } else if ("2.5".equals(version)) {
738                 javaeeNamespace = XmlIdentifiers.JAVAEE_5_NS;
739                 webXmlSchemaLocation = XmlIdentifiers.WEB_25_XSD;
740             } else if ("3.0".equals(version)) {
741                 javaeeNamespace = XmlIdentifiers.JAVAEE_6_NS;
742                 webXmlSchemaLocation = XmlIdentifiers.WEB_30_XSD;
743             } else if ("3.1".equals(version)) {
744                 javaeeNamespace = XmlIdentifiers.JAVAEE_7_NS;
745                 webXmlSchemaLocation = XmlIdentifiers.WEB_31_XSD;
746             } else if ("4.0".equals(version)) {
747                 javaeeNamespace = XmlIdentifiers.JAVAEE_8_NS;
748                 webXmlSchemaLocation = XmlIdentifiers.WEB_40_XSD;
749             }
750             sb.append("<web-app xmlns=\"");
751             sb.append(javaeeNamespace);
752             sb.append("\"\n");
753             sb.append("         xmlns:xsi=");
754             sb.append("\"http://www.w3.org/2001/XMLSchema-instance\"\n");
755             sb.append("         xsi:schemaLocation=\"");
756             sb.append(javaeeNamespace);
757             sb.append(" ");
758             sb.append(webXmlSchemaLocation);
759             sb.append("\"\n");
760             sb.append("         version=\"");
761             sb.append(getVersion());
762             sb.append("\"");
763             if ("2.4".equals(version)) {
764                 sb.append(">\n\n");
765             } else {
766                 sb.append("\n         metadata-complete=\"true\">\n\n");
767             }
768         }
769
770         appendElement(sb, INDENT2, "display-name", displayName);
771
772         if (isDistributable()) {
773             sb.append("  <distributable/>\n\n");
774         }
775
776         for (Map.Entry<String, String> entry : contextParams.entrySet()) {
777             sb.append("  <context-param>\n");
778             appendElement(sb, INDENT4, "param-name", entry.getKey());
779             appendElement(sb, INDENT4, "param-value", entry.getValue());
780             sb.append("  </context-param>\n");
781         }
782         sb.append('\n');
783
784         // Filters were introduced in Servlet 2.3
785         if (getMajorVersion() > 2 || getMinorVersion() > 2) {
786             for (Map.Entry<String, FilterDef> entry : filters.entrySet()) {
787                 FilterDef filterDef = entry.getValue();
788                 sb.append("  <filter>\n");
789                 appendElement(sb, INDENT4, "description",
790                         filterDef.getDescription());
791                 appendElement(sb, INDENT4, "display-name",
792                         filterDef.getDisplayName());
793                 appendElement(sb, INDENT4, "filter-name",
794                         filterDef.getFilterName());
795                 appendElement(sb, INDENT4, "filter-class",
796                         filterDef.getFilterClass());
797                 // Async support was introduced for Servlet 3.0 onwards
798                 if (getMajorVersion() != 2) {
799                     appendElement(sb, INDENT4, "async-supported",
800                             filterDef.getAsyncSupported());
801                 }
802                 for (Map.Entry<String, String> param :
803                         filterDef.getParameterMap().entrySet()) {
804                     sb.append("    <init-param>\n");
805                     appendElement(sb, INDENT6, "param-name", param.getKey());
806                     appendElement(sb, INDENT6, "param-value", param.getValue());
807                     sb.append("    </init-param>\n");
808                 }
809                 sb.append("  </filter>\n");
810             }
811             sb.append('\n');
812
813             for (FilterMap filterMap : filterMaps) {
814                 sb.append("  <filter-mapping>\n");
815                 appendElement(sb, INDENT4, "filter-name",
816                         filterMap.getFilterName());
817                 if (filterMap.getMatchAllServletNames()) {
818                     sb.append("    <servlet-name>*</servlet-name>\n");
819                 } else {
820                     for (String servletName : filterMap.getServletNames()) {
821                         appendElement(sb, INDENT4, "servlet-name", servletName);
822                     }
823                 }
824                 if (filterMap.getMatchAllUrlPatterns()) {
825                     sb.append("    <url-pattern>*</url-pattern>\n");
826                 } else {
827                     for (String urlPattern : filterMap.getURLPatterns()) {
828                         appendElement(sb, INDENT4, "url-pattern", encodeUrl(urlPattern));
829                     }
830                 }
831                 // dispatcher was added in Servlet 2.4
832                 if (getMajorVersion() > 2 || getMinorVersion() > 3) {
833                     for (String dispatcher : filterMap.getDispatcherNames()) {
834                         if (getMajorVersion() == 2 &&
835                                 DispatcherType.ASYNC.name().equals(dispatcher)) {
836                             continue;
837                         }
838                         appendElement(sb, INDENT4, "dispatcher", dispatcher);
839                     }
840                 }
841                 sb.append("  </filter-mapping>\n");
842             }
843             sb.append('\n');
844         }
845
846         // Listeners were introduced in Servlet 2.3
847         if (getMajorVersion() > 2 || getMinorVersion() > 2) {
848             for (String listener : listeners) {
849                 sb.append("  <listener>\n");
850                 appendElement(sb, INDENT4, "listener-class", listener);
851                 sb.append("  </listener>\n");
852             }
853             sb.append('\n');
854         }
855
856         for (Map.Entry<String, ServletDef> entry : servlets.entrySet()) {
857             ServletDef servletDef = entry.getValue();
858             sb.append("  <servlet>\n");
859             appendElement(sb, INDENT4, "description",
860                     servletDef.getDescription());
861             appendElement(sb, INDENT4, "display-name",
862                     servletDef.getDisplayName());
863             appendElement(sb, INDENT4, "servlet-name", entry.getKey());
864             appendElement(sb, INDENT4, "servlet-class",
865                     servletDef.getServletClass());
866             appendElement(sb, INDENT4, "jsp-file", servletDef.getJspFile());
867             for (Map.Entry<String, String> param :
868                     servletDef.getParameterMap().entrySet()) {
869                 sb.append("    <init-param>\n");
870                 appendElement(sb, INDENT6, "param-name", param.getKey());
871                 appendElement(sb, INDENT6, "param-value", param.getValue());
872                 sb.append("    </init-param>\n");
873             }
874             appendElement(sb, INDENT4, "load-on-startup",
875                     servletDef.getLoadOnStartup());
876             appendElement(sb, INDENT4, "enabled", servletDef.getEnabled());
877             // Async support was introduced for Servlet 3.0 onwards
878             if (getMajorVersion() != 2) {
879                 appendElement(sb, INDENT4, "async-supported",
880                         servletDef.getAsyncSupported());
881             }
882             // servlet/run-as was introduced in Servlet 2.3
883             if (getMajorVersion() > 2 || getMinorVersion() > 2) {
884                 if (servletDef.getRunAs() != null) {
885                     sb.append("    <run-as>\n");
886                     appendElement(sb, INDENT6, "role-name", servletDef.getRunAs());
887                     sb.append("    </run-as>\n");
888                 }
889             }
890             for (SecurityRoleRef roleRef : servletDef.getSecurityRoleRefs()) {
891                 sb.append("    <security-role-ref>\n");
892                 appendElement(sb, INDENT6, "role-name", roleRef.getName());
893                 appendElement(sb, INDENT6, "role-link", roleRef.getLink());
894                 sb.append("    </security-role-ref>\n");
895             }
896             // multipart-config was added in Servlet 3.0
897             if (getMajorVersion() != 2) {
898                 MultipartDef multipartDef = servletDef.getMultipartDef();
899                 if (multipartDef != null) {
900                     sb.append("    <multipart-config>\n");
901                     appendElement(sb, INDENT6, "location",
902                             multipartDef.getLocation());
903                     appendElement(sb, INDENT6, "max-file-size",
904                             multipartDef.getMaxFileSize());
905                     appendElement(sb, INDENT6, "max-request-size",
906                             multipartDef.getMaxRequestSize());
907                     appendElement(sb, INDENT6, "file-size-threshold",
908                             multipartDef.getFileSizeThreshold());
909                     sb.append("    </multipart-config>\n");
910                 }
911             }
912             sb.append("  </servlet>\n");
913         }
914         sb.append('\n');
915
916         for (Map.Entry<String, String> entry : servletMappings.entrySet()) {
917             sb.append("  <servlet-mapping>\n");
918             appendElement(sb, INDENT4, "servlet-name", entry.getValue());
919             appendElement(sb, INDENT4, "url-pattern", encodeUrl(entry.getKey()));
920             sb.append("  </servlet-mapping>\n");
921         }
922         sb.append('\n');
923
924         if (sessionConfig != null) {
925             sb.append("  <session-config>\n");
926             appendElement(sb, INDENT4, "session-timeout",
927                     sessionConfig.getSessionTimeout());
928             if (majorVersion >= 3) {
929                 sb.append("    <cookie-config>\n");
930                 appendElement(sb, INDENT6, "name", sessionConfig.getCookieName());
931                 appendElement(sb, INDENT6, "domain",
932                         sessionConfig.getCookieDomain());
933                 appendElement(sb, INDENT6, "path", sessionConfig.getCookiePath());
934                 appendElement(sb, INDENT6, "comment",
935                         sessionConfig.getCookieComment());
936                 appendElement(sb, INDENT6, "http-only",
937                         sessionConfig.getCookieHttpOnly());
938                 appendElement(sb, INDENT6, "secure",
939                         sessionConfig.getCookieSecure());
940                 appendElement(sb, INDENT6, "max-age",
941                         sessionConfig.getCookieMaxAge());
942                 sb.append("    </cookie-config>\n");
943                 for (SessionTrackingMode stm :
944                         sessionConfig.getSessionTrackingModes()) {
945                     appendElement(sb, INDENT4, "tracking-mode", stm.name());
946                 }
947             }
948             sb.append("  </session-config>\n\n");
949         }
950
951         for (Map.Entry<String, String> entry : mimeMappings.entrySet()) {
952             sb.append("  <mime-mapping>\n");
953             appendElement(sb, INDENT4, "extension", entry.getKey());
954             appendElement(sb, INDENT4, "mime-type", entry.getValue());
955             sb.append("  </mime-mapping>\n");
956         }
957         sb.append('\n');
958
959         if (welcomeFiles.size() > 0) {
960             sb.append("  <welcome-file-list>\n");
961             for (String welcomeFile : welcomeFiles) {
962                 appendElement(sb, INDENT4, "welcome-file", welcomeFile);
963             }
964             sb.append("  </welcome-file-list>\n\n");
965         }
966
967         for (ErrorPage errorPage : errorPages.values()) {
968             String exceptionType = errorPage.getExceptionType();
969             int errorCode = errorPage.getErrorCode();
970
971             if (exceptionType == null && errorCode == 0 && getMajorVersion() == 2) {
972                 // Default error pages are only supported from 3.0 onwards
973                 continue;
974             }
975             sb.append("  <error-page>\n");
976             if (errorPage.getExceptionType() != null) {
977                 appendElement(sb, INDENT4, "exception-type", exceptionType);
978             } else if (errorPage.getErrorCode() > 0) {
979                 appendElement(sb, INDENT4, "error-code",
980                         Integer.toString(errorCode));
981             }
982             appendElement(sb, INDENT4, "location", errorPage.getLocation());
983             sb.append("  </error-page>\n");
984         }
985         sb.append('\n');
986
987         // jsp-config was added in Servlet 2.4. Prior to that, tag-libs was used
988         // directly and jsp-property-group did not exist
989         if (taglibs.size() > 0 || jspPropertyGroups.size() > 0) {
990             if (getMajorVersion() > 2 || getMinorVersion() > 3) {
991                 sb.append("  <jsp-config>\n");
992             }
993             for (Map.Entry<String, String> entry : taglibs.entrySet()) {
994                 sb.append("    <taglib>\n");
995                 appendElement(sb, INDENT6, "taglib-uri", entry.getKey());
996                 appendElement(sb, INDENT6, "taglib-location", entry.getValue());
997                 sb.append("    </taglib>\n");
998             }
999             if (getMajorVersion() > 2 || getMinorVersion() > 3) {
1000                 for (JspPropertyGroup jpg : jspPropertyGroups) {
1001                     sb.append("    <jsp-property-group>\n");
1002                     for (String urlPattern : jpg.getUrlPatterns()) {
1003                         appendElement(sb, INDENT6, "url-pattern", encodeUrl(urlPattern));
1004                     }
1005                     appendElement(sb, INDENT6, "el-ignored", jpg.getElIgnored());
1006                     appendElement(sb, INDENT6, "page-encoding",
1007                             jpg.getPageEncoding());
1008                     appendElement(sb, INDENT6, "scripting-invalid",
1009                             jpg.getScriptingInvalid());
1010                     appendElement(sb, INDENT6, "is-xml", jpg.getIsXml());
1011                     for (String prelude : jpg.getIncludePreludes()) {
1012                         appendElement(sb, INDENT6, "include-prelude", prelude);
1013                     }
1014                     for (String coda : jpg.getIncludeCodas()) {
1015                         appendElement(sb, INDENT6, "include-coda", coda);
1016                     }
1017                     appendElement(sb, INDENT6, "deferred-syntax-allowed-as-literal",
1018                             jpg.getDeferredSyntax());
1019                     appendElement(sb, INDENT6, "trim-directive-whitespaces",
1020                             jpg.getTrimWhitespace());
1021                     appendElement(sb, INDENT6, "default-content-type",
1022                             jpg.getDefaultContentType());
1023                     appendElement(sb, INDENT6, "buffer", jpg.getBuffer());
1024                     appendElement(sb, INDENT6, "error-on-undeclared-namespace",
1025                             jpg.getErrorOnUndeclaredNamespace());
1026                     sb.append("    </jsp-property-group>\n");
1027                 }
1028                 sb.append("  </jsp-config>\n\n");
1029             }
1030         }
1031
1032         // resource-env-ref was introduced in Servlet 2.3
1033         if (getMajorVersion() > 2 || getMinorVersion() > 2) {
1034             for (ContextResourceEnvRef resourceEnvRef : resourceEnvRefs.values()) {
1035                 sb.append("  <resource-env-ref>\n");
1036                 appendElement(sb, INDENT4, "description",
1037                         resourceEnvRef.getDescription());
1038                 appendElement(sb, INDENT4, "resource-env-ref-name",
1039                         resourceEnvRef.getName());
1040                 appendElement(sb, INDENT4, "resource-env-ref-type",
1041                         resourceEnvRef.getType());
1042                 appendElement(sb, INDENT4, "mapped-name",
1043                         resourceEnvRef.getProperty("mappedName"));
1044                 for (InjectionTarget target :
1045                         resourceEnvRef.getInjectionTargets()) {
1046                     sb.append("    <injection-target>\n");
1047                     appendElement(sb, INDENT6, "injection-target-class",
1048                             target.getTargetClass());
1049                     appendElement(sb, INDENT6, "injection-target-name",
1050                             target.getTargetName());
1051                     sb.append("    </injection-target>\n");
1052                 }
1053                 appendElement(sb, INDENT4, "lookup-name", resourceEnvRef.getLookupName());
1054                 sb.append("  </resource-env-ref>\n");
1055             }
1056             sb.append('\n');
1057         }
1058
1059         for (ContextResource resourceRef : resourceRefs.values()) {
1060             sb.append("  <resource-ref>\n");
1061             appendElement(sb, INDENT4, "description",
1062                     resourceRef.getDescription());
1063             appendElement(sb, INDENT4, "res-ref-name", resourceRef.getName());
1064             appendElement(sb, INDENT4, "res-type", resourceRef.getType());
1065             appendElement(sb, INDENT4, "res-auth", resourceRef.getAuth());
1066             // resource-ref/res-sharing-scope was introduced in Servlet 2.3
1067             if (getMajorVersion() > 2 || getMinorVersion() > 2) {
1068                 appendElement(sb, INDENT4, "res-sharing-scope", resourceRef.getScope());
1069             }
1070             appendElement(sb, INDENT4, "mapped-name", resourceRef.getProperty("mappedName"));
1071             for (InjectionTarget target : resourceRef.getInjectionTargets()) {
1072                 sb.append("    <injection-target>\n");
1073                 appendElement(sb, INDENT6, "injection-target-class",
1074                         target.getTargetClass());
1075                 appendElement(sb, INDENT6, "injection-target-name",
1076                         target.getTargetName());
1077                 sb.append("    </injection-target>\n");
1078             }
1079             appendElement(sb, INDENT4, "lookup-name", resourceRef.getLookupName());
1080             sb.append("  </resource-ref>\n");
1081         }
1082         sb.append('\n');
1083
1084         for (SecurityConstraint constraint : securityConstraints) {
1085             sb.append("  <security-constraint>\n");
1086             // security-constraint/display-name was introduced in Servlet 2.3
1087             if (getMajorVersion() > 2 || getMinorVersion() > 2) {
1088                 appendElement(sb, INDENT4, "display-name",
1089                         constraint.getDisplayName());
1090             }
1091             for (SecurityCollection collection : constraint.findCollections()) {
1092                 sb.append("    <web-resource-collection>\n");
1093                 appendElement(sb, INDENT6, "web-resource-name",
1094                         collection.getName());
1095                 appendElement(sb, INDENT6, "description",
1096                         collection.getDescription());
1097                 for (String urlPattern : collection.findPatterns()) {
1098                     appendElement(sb, INDENT6, "url-pattern", encodeUrl(urlPattern));
1099                 }
1100                 for (String method : collection.findMethods()) {
1101                     appendElement(sb, INDENT6, "http-method", method);
1102                 }
1103                 for (String method : collection.findOmittedMethods()) {
1104                     appendElement(sb, INDENT6, "http-method-omission", method);
1105                 }
1106                 sb.append("    </web-resource-collection>\n");
1107             }
1108             if (constraint.findAuthRoles().length > 0) {
1109                 sb.append("    <auth-constraint>\n");
1110                 for (String role : constraint.findAuthRoles()) {
1111                     appendElement(sb, INDENT6, "role-name", role);
1112                 }
1113                 sb.append("    </auth-constraint>\n");
1114             }
1115             if (constraint.getUserConstraint() != null) {
1116                 sb.append("    <user-data-constraint>\n");
1117                 appendElement(sb, INDENT6, "transport-guarantee",
1118                         constraint.getUserConstraint());
1119                 sb.append("    </user-data-constraint>\n");
1120             }
1121             sb.append("  </security-constraint>\n");
1122         }
1123         sb.append('\n');
1124
1125         if (loginConfig != null) {
1126             sb.append("  <login-config>\n");
1127             appendElement(sb, INDENT4, "auth-method",
1128                     loginConfig.getAuthMethod());
1129             appendElement(sb,INDENT4, "realm-name",
1130                     loginConfig.getRealmName());
1131             if (loginConfig.getErrorPage() != null ||
1132                         loginConfig.getLoginPage() != null) {
1133                 sb.append("    <form-login-config>\n");
1134                 appendElement(sb, INDENT6, "form-login-page",
1135                         loginConfig.getLoginPage());
1136                 appendElement(sb, INDENT6, "form-error-page",
1137                         loginConfig.getErrorPage());
1138                 sb.append("    </form-login-config>\n");
1139             }
1140             sb.append("  </login-config>\n\n");
1141         }
1142
1143         for (String roleName : securityRoles) {
1144             sb.append("  <security-role>\n");
1145             appendElement(sb, INDENT4, "role-name", roleName);
1146             sb.append("  </security-role>\n");
1147         }
1148
1149         for (ContextEnvironment envEntry : envEntries.values()) {
1150             sb.append("  <env-entry>\n");
1151             appendElement(sb, INDENT4, "description",
1152                     envEntry.getDescription());
1153             appendElement(sb, INDENT4, "env-entry-name", envEntry.getName());
1154             appendElement(sb, INDENT4, "env-entry-type", envEntry.getType());
1155             appendElement(sb, INDENT4, "env-entry-value", envEntry.getValue());
1156             appendElement(sb, INDENT4, "mapped-name", envEntry.getProperty("mappedName"));
1157             for (InjectionTarget target : envEntry.getInjectionTargets()) {
1158                 sb.append("    <injection-target>\n");
1159                 appendElement(sb, INDENT6, "injection-target-class",
1160                         target.getTargetClass());
1161                 appendElement(sb, INDENT6, "injection-target-name",
1162                         target.getTargetName());
1163                 sb.append("    </injection-target>\n");
1164             }
1165             appendElement(sb, INDENT4, "lookup-name", envEntry.getLookupName());
1166             sb.append("  </env-entry>\n");
1167         }
1168         sb.append('\n');
1169
1170         for (ContextEjb ejbRef : ejbRefs.values()) {
1171             sb.append("  <ejb-ref>\n");
1172             appendElement(sb, INDENT4, "description", ejbRef.getDescription());
1173             appendElement(sb, INDENT4, "ejb-ref-name", ejbRef.getName());
1174             appendElement(sb, INDENT4, "ejb-ref-type", ejbRef.getType());
1175             appendElement(sb, INDENT4, "home", ejbRef.getHome());
1176             appendElement(sb, INDENT4, "remote", ejbRef.getRemote());
1177             appendElement(sb, INDENT4, "ejb-link", ejbRef.getLink());
1178             appendElement(sb, INDENT4, "mapped-name", ejbRef.getProperty("mappedName"));
1179             for (InjectionTarget target : ejbRef.getInjectionTargets()) {
1180                 sb.append("    <injection-target>\n");
1181                 appendElement(sb, INDENT6, "injection-target-class",
1182                         target.getTargetClass());
1183                 appendElement(sb, INDENT6, "injection-target-name",
1184                         target.getTargetName());
1185                 sb.append("    </injection-target>\n");
1186             }
1187             appendElement(sb, INDENT4, "lookup-name", ejbRef.getLookupName());
1188             sb.append("  </ejb-ref>\n");
1189         }
1190         sb.append('\n');
1191
1192         // ejb-local-ref was introduced in Servlet 2.3
1193         if (getMajorVersion() > 2 || getMinorVersion() > 2) {
1194             for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
1195                 sb.append("  <ejb-local-ref>\n");
1196                 appendElement(sb, INDENT4, "description",
1197                         ejbLocalRef.getDescription());
1198                 appendElement(sb, INDENT4, "ejb-ref-name", ejbLocalRef.getName());
1199                 appendElement(sb, INDENT4, "ejb-ref-type", ejbLocalRef.getType());
1200                 appendElement(sb, INDENT4, "local-home", ejbLocalRef.getHome());
1201                 appendElement(sb, INDENT4, "local", ejbLocalRef.getLocal());
1202                 appendElement(sb, INDENT4, "ejb-link", ejbLocalRef.getLink());
1203                 appendElement(sb, INDENT4, "mapped-name", ejbLocalRef.getProperty("mappedName"));
1204                 for (InjectionTarget target : ejbLocalRef.getInjectionTargets()) {
1205                     sb.append("    <injection-target>\n");
1206                     appendElement(sb, INDENT6, "injection-target-class",
1207                             target.getTargetClass());
1208                     appendElement(sb, INDENT6, "injection-target-name",
1209                             target.getTargetName());
1210                     sb.append("    </injection-target>\n");
1211                 }
1212                 appendElement(sb, INDENT4, "lookup-name", ejbLocalRef.getLookupName());
1213                 sb.append("  </ejb-local-ref>\n");
1214             }
1215             sb.append('\n');
1216         }
1217
1218         // service-ref was introduced in Servlet 2.4
1219         if (getMajorVersion() > 2 || getMinorVersion() > 3) {
1220             for (ContextService serviceRef : serviceRefs.values()) {
1221                 sb.append("  <service-ref>\n");
1222                 appendElement(sb, INDENT4, "description",
1223                         serviceRef.getDescription());
1224                 appendElement(sb, INDENT4, "display-name",
1225                         serviceRef.getDisplayname());
1226                 appendElement(sb, INDENT4, "service-ref-name",
1227                         serviceRef.getName());
1228                 appendElement(sb, INDENT4, "service-interface",
1229                         serviceRef.getInterface());
1230                 appendElement(sb, INDENT4, "service-ref-type",
1231                         serviceRef.getType());
1232                 appendElement(sb, INDENT4, "wsdl-file", serviceRef.getWsdlfile());
1233                 appendElement(sb, INDENT4, "jaxrpc-mapping-file",
1234                         serviceRef.getJaxrpcmappingfile());
1235                 String qname = serviceRef.getServiceqnameNamespaceURI();
1236                 if (qname != null) {
1237                     qname = qname + ":";
1238                 }
1239                 qname = qname + serviceRef.getServiceqnameLocalpart();
1240                 appendElement(sb, INDENT4, "service-qname", qname);
1241                 Iterator<String> endpointIter = serviceRef.getServiceendpoints();
1242                 while (endpointIter.hasNext()) {
1243                     String endpoint = endpointIter.next();
1244                     sb.append("    <port-component-ref>\n");
1245                     appendElement(sb, INDENT6, "service-endpoint-interface",
1246                             endpoint);
1247                     appendElement(sb, INDENT6, "port-component-link",
1248                             serviceRef.getProperty(endpoint));
1249                     sb.append("    </port-component-ref>\n");
1250                 }
1251                 Iterator<String> handlerIter = serviceRef.getHandlers();
1252                 while (handlerIter.hasNext()) {
1253                     String handler = handlerIter.next();
1254                     sb.append("    <handler>\n");
1255                     ContextHandler ch = serviceRef.getHandler(handler);
1256                     appendElement(sb, INDENT6, "handler-name", ch.getName());
1257                     appendElement(sb, INDENT6, "handler-class",
1258                             ch.getHandlerclass());
1259                     sb.append("    </handler>\n");
1260                 }
1261                 // TODO handler-chains
1262                 appendElement(sb, INDENT4, "mapped-name", serviceRef.getProperty("mappedName"));
1263                 for (InjectionTarget target : serviceRef.getInjectionTargets()) {
1264                     sb.append("    <injection-target>\n");
1265                     appendElement(sb, INDENT6, "injection-target-class",
1266                             target.getTargetClass());
1267                     appendElement(sb, INDENT6, "injection-target-name",
1268                             target.getTargetName());
1269                     sb.append("    </injection-target>\n");
1270                 }
1271                 appendElement(sb, INDENT4, "lookup-name", serviceRef.getLookupName());
1272                 sb.append("  </service-ref>\n");
1273             }
1274             sb.append('\n');
1275         }
1276
1277         if (!postConstructMethods.isEmpty()) {
1278             for (Entry<String, String> entry : postConstructMethods
1279                     .entrySet()) {
1280                 sb.append("  <post-construct>\n");
1281                 appendElement(sb, INDENT4, "lifecycle-callback-class",
1282                         entry.getKey());
1283                 appendElement(sb, INDENT4, "lifecycle-callback-method",
1284                         entry.getValue());
1285                 sb.append("  </post-construct>\n");
1286             }
1287             sb.append('\n');
1288         }
1289
1290         if (!preDestroyMethods.isEmpty()) {
1291             for (Entry<String, String> entry : preDestroyMethods
1292                     .entrySet()) {
1293                 sb.append("  <pre-destroy>\n");
1294                 appendElement(sb, INDENT4, "lifecycle-callback-class",
1295                         entry.getKey());
1296                 appendElement(sb, INDENT4, "lifecycle-callback-method",
1297                         entry.getValue());
1298                 sb.append("  </pre-destroy>\n");
1299             }
1300             sb.append('\n');
1301         }
1302
1303         // message-destination-ref, message-destination were introduced in
1304         // Servlet 2.4
1305         if (getMajorVersion() > 2 || getMinorVersion() > 3) {
1306             for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
1307                 sb.append("  <message-destination-ref>\n");
1308                 appendElement(sb, INDENT4, "description", mdr.getDescription());
1309                 appendElement(sb, INDENT4, "message-destination-ref-name",
1310                         mdr.getName());
1311                 appendElement(sb, INDENT4, "message-destination-type",
1312                         mdr.getType());
1313                 appendElement(sb, INDENT4, "message-destination-usage",
1314                         mdr.getUsage());
1315                 appendElement(sb, INDENT4, "message-destination-link",
1316                         mdr.getLink());
1317                 appendElement(sb, INDENT4, "mapped-name", mdr.getProperty("mappedName"));
1318                 for (InjectionTarget target : mdr.getInjectionTargets()) {
1319                     sb.append("    <injection-target>\n");
1320                     appendElement(sb, INDENT6, "injection-target-class",
1321                             target.getTargetClass());
1322                     appendElement(sb, INDENT6, "injection-target-name",
1323                             target.getTargetName());
1324                     sb.append("    </injection-target>\n");
1325                 }
1326                 appendElement(sb, INDENT4, "lookup-name", mdr.getLookupName());
1327                 sb.append("  </message-destination-ref>\n");
1328             }
1329             sb.append('\n');
1330
1331             for (MessageDestination md : messageDestinations.values()) {
1332                 sb.append("  <message-destination>\n");
1333                 appendElement(sb, INDENT4, "description", md.getDescription());
1334                 appendElement(sb, INDENT4, "display-name", md.getDisplayName());
1335                 appendElement(sb, INDENT4, "message-destination-name",
1336                         md.getName());
1337                 appendElement(sb, INDENT4, "mapped-name", md.getProperty("mappedName"));
1338                 appendElement(sb, INDENT4, "lookup-name", md.getLookupName());
1339                 sb.append("  </message-destination>\n");
1340             }
1341             sb.append('\n');
1342         }
1343
1344         // locale-encoding-mapping-list was introduced in Servlet 2.4
1345         if (getMajorVersion() > 2 || getMinorVersion() > 3) {
1346             if (localeEncodingMappings.size() > 0) {
1347                 sb.append("  <locale-encoding-mapping-list>\n");
1348                 for (Map.Entry<String, String> entry :
1349                         localeEncodingMappings.entrySet()) {
1350                     sb.append("    <locale-encoding-mapping>\n");
1351                     appendElement(sb, INDENT6, "locale", entry.getKey());
1352                     appendElement(sb, INDENT6, "encoding", entry.getValue());
1353                     sb.append("    </locale-encoding-mapping>\n");
1354                 }
1355                 sb.append("  </locale-encoding-mapping-list>\n");
1356                 sb.append("\n");
1357             }
1358         }
1359
1360         // deny-uncovered-http-methods was introduced in Servlet 3.1
1361         if (getMajorVersion() > 3 ||
1362                 (getMajorVersion() == 3 && getMinorVersion() > 0)) {
1363             if (denyUncoveredHttpMethods) {
1364                 sb.append("  <deny-uncovered-http-methods/>");
1365                 sb.append("\n");
1366             }
1367         }
1368
1369         // request-encoding and response-encoding was introduced in Servlet 4.0
1370         if (getMajorVersion() >= 4) {
1371             appendElement(sb, INDENT2, "request-character-encoding", requestCharacterEncoding);
1372             appendElement(sb, INDENT2, "response-character-encoding", responseCharacterEncoding);
1373         }
1374         sb.append("</web-app>");
1375         return sb.toString();
1376     }
1377
1378
1379     private String encodeUrl(String input) {
1380         try {
1381             return URLEncoder.encode(input, "UTF-8");
1382         } catch (UnsupportedEncodingException e) {
1383             // Impossible. UTF-8 is a required character set
1384             return null;
1385         }
1386     }
1387
1388
1389     private static void appendElement(StringBuilder sb, String indent,
1390             String elementName, String value) {
1391         if (value == null) {
1392             return;
1393         }
1394         if (value.length() == 0) {
1395             sb.append(indent);
1396             sb.append('<');
1397             sb.append(elementName);
1398             sb.append("/>\n");
1399         } else {
1400             sb.append(indent);
1401             sb.append('<');
1402             sb.append(elementName);
1403             sb.append('>');
1404             sb.append(Escape.xml(value));
1405             sb.append("</");
1406             sb.append(elementName);
1407             sb.append(">\n");
1408         }
1409     }
1410
1411     private static void appendElement(StringBuilder sb, String indent,
1412             String elementName, Object value) {
1413         if (value == nullreturn;
1414         appendElement(sb, indent, elementName, value.toString());
1415     }
1416
1417
1418     /**
1419      * Merge the supplied web fragments into this main web.xml.
1420      *
1421      * @param fragments     The fragments to merge in
1422      * @return <code>true</code> if merge is successful, else
1423      *         <code>false</code>
1424      */

1425     public boolean merge(Set<WebXml> fragments) {
1426         // As far as possible, process in alphabetical order so it is easy to
1427         // check everything is present
1428
1429         // Merge rules vary from element to element. See SRV.8.2.3
1430
1431         WebXml temp = new WebXml();
1432
1433         for (WebXml fragment : fragments) {
1434             if (!mergeMap(fragment.getContextParams(), contextParams,
1435                     temp.getContextParams(), fragment, "Context Parameter")) {
1436                 return false;
1437             }
1438         }
1439         contextParams.putAll(temp.getContextParams());
1440
1441         if (displayName == null) {
1442             for (WebXml fragment : fragments) {
1443                 String value = fragment.getDisplayName();
1444                 if (value != null) {
1445                     if (temp.getDisplayName() == null) {
1446                         temp.setDisplayName(value);
1447                     } else {
1448                         log.error(sm.getString(
1449                                 "webXml.mergeConflictDisplayName",
1450                                 fragment.getName(),
1451                                 fragment.getURL()));
1452                         return false;
1453                     }
1454                 }
1455             }
1456             displayName = temp.getDisplayName();
1457         }
1458
1459         // Note: Not permitted in fragments but we also use fragments for
1460         //       per-Host and global defaults so they may appear there
1461         if (!denyUncoveredHttpMethods) {
1462             for (WebXml fragment : fragments) {
1463                 if (fragment.getDenyUncoveredHttpMethods()) {
1464                     denyUncoveredHttpMethods = true;
1465                     break;
1466                 }
1467             }
1468         }
1469         if (requestCharacterEncoding == null) {
1470             for (WebXml fragment : fragments) {
1471                 if (fragment.getRequestCharacterEncoding() != null) {
1472                     requestCharacterEncoding = fragment.getRequestCharacterEncoding();
1473                 }
1474             }
1475         }
1476         if (responseCharacterEncoding == null) {
1477             for (WebXml fragment : fragments) {
1478                 if (fragment.getResponseCharacterEncoding() != null) {
1479                     responseCharacterEncoding = fragment.getResponseCharacterEncoding();
1480                 }
1481             }
1482         }
1483
1484         if (distributable) {
1485             for (WebXml fragment : fragments) {
1486                 if (!fragment.isDistributable()) {
1487                     distributable = false;
1488                     break;
1489                 }
1490             }
1491         }
1492
1493         for (WebXml fragment : fragments) {
1494             if (!mergeResourceMap(fragment.getEjbLocalRefs(), ejbLocalRefs,
1495                     temp.getEjbLocalRefs(), fragment)) {
1496                 return false;
1497             }
1498         }
1499         ejbLocalRefs.putAll(temp.getEjbLocalRefs());
1500
1501         for (WebXml fragment : fragments) {
1502             if (!mergeResourceMap(fragment.getEjbRefs(), ejbRefs,
1503                     temp.getEjbRefs(), fragment)) {
1504                 return false;
1505             }
1506         }
1507         ejbRefs.putAll(temp.getEjbRefs());
1508
1509         for (WebXml fragment : fragments) {
1510             if (!mergeResourceMap(fragment.getEnvEntries(), envEntries,
1511                     temp.getEnvEntries(), fragment)) {
1512                 return false;
1513             }
1514         }
1515         envEntries.putAll(temp.getEnvEntries());
1516
1517         for (WebXml fragment : fragments) {
1518             if (!mergeMap(fragment.getErrorPages(), errorPages,
1519                     temp.getErrorPages(), fragment, "Error Page")) {
1520                 return false;
1521             }
1522         }
1523         errorPages.putAll(temp.getErrorPages());
1524
1525         // As per 'clarification' from the Servlet EG, filter definitions in the
1526         // main web.xml override those in fragments and those in fragments
1527         // override those in annotations
1528         List<FilterMap> filterMapsToAdd = new ArrayList<>();
1529         for (WebXml fragment : fragments) {
1530             for (FilterMap filterMap : fragment.getFilterMappings()) {
1531                 if (!filterMappingNames.contains(filterMap.getFilterName())) {
1532                     filterMapsToAdd.add(filterMap);
1533                 }
1534             }
1535         }
1536         for (FilterMap filterMap : filterMapsToAdd) {
1537             // Additive
1538             addFilterMapping(filterMap);
1539         }
1540
1541         for (WebXml fragment : fragments) {
1542             for (Map.Entry<String,FilterDef> entry :
1543                     fragment.getFilters().entrySet()) {
1544                 if (filters.containsKey(entry.getKey())) {
1545                     mergeFilter(entry.getValue(),
1546                             filters.get(entry.getKey()), false);
1547                 } else {
1548                     if (temp.getFilters().containsKey(entry.getKey())) {
1549                         if (!(mergeFilter(entry.getValue(),
1550                                 temp.getFilters().get(entry.getKey()), true))) {
1551                             log.error(sm.getString(
1552                                     "webXml.mergeConflictFilter",
1553                                     entry.getKey(),
1554                                     fragment.getName(),
1555                                     fragment.getURL()));
1556
1557                             return false;
1558                         }
1559                     } else {
1560                         temp.getFilters().put(entry.getKey(), entry.getValue());
1561                     }
1562                 }
1563             }
1564         }
1565         filters.putAll(temp.getFilters());
1566
1567         for (WebXml fragment : fragments) {
1568             for (JspPropertyGroup jspPropertyGroup :
1569                     fragment.getJspPropertyGroups()) {
1570                 // Always additive
1571                 addJspPropertyGroup(jspPropertyGroup);
1572             }
1573         }
1574
1575         for (WebXml fragment : fragments) {
1576             for (String listener : fragment.getListeners()) {
1577                 // Always additive
1578                 addListener(listener);
1579             }
1580         }
1581
1582         for (WebXml fragment : fragments) {
1583             if (!mergeMap(fragment.getLocaleEncodingMappings(),
1584                     localeEncodingMappings, temp.getLocaleEncodingMappings(),
1585                     fragment, "Locale Encoding Mapping")) {
1586                 return false;
1587             }
1588         }
1589         localeEncodingMappings.putAll(temp.getLocaleEncodingMappings());
1590
1591         if (getLoginConfig() == null) {
1592             LoginConfig tempLoginConfig = null;
1593             for (WebXml fragment : fragments) {
1594                 LoginConfig fragmentLoginConfig = fragment.loginConfig;
1595                 if (fragmentLoginConfig != null) {
1596                     if (tempLoginConfig == null ||
1597                             fragmentLoginConfig.equals(tempLoginConfig)) {
1598                         tempLoginConfig = fragmentLoginConfig;
1599                     } else {
1600                         log.error(sm.getString(
1601                                 "webXml.mergeConflictLoginConfig",
1602                                 fragment.getName(),
1603                                 fragment.getURL()));
1604                     }
1605                 }
1606             }
1607             loginConfig = tempLoginConfig;
1608         }
1609
1610         for (WebXml fragment : fragments) {
1611             if (!mergeResourceMap(fragment.getMessageDestinationRefs(), messageDestinationRefs,
1612                     temp.getMessageDestinationRefs(), fragment)) {
1613                 return false;
1614             }
1615         }
1616         messageDestinationRefs.putAll(temp.getMessageDestinationRefs());
1617
1618         for (WebXml fragment : fragments) {
1619             if (!mergeResourceMap(fragment.getMessageDestinations(), messageDestinations,
1620                     temp.getMessageDestinations(), fragment)) {
1621                 return false;
1622             }
1623         }
1624         messageDestinations.putAll(temp.getMessageDestinations());
1625
1626         for (WebXml fragment : fragments) {
1627             if (!mergeMap(fragment.getMimeMappings(), mimeMappings,
1628                     temp.getMimeMappings(), fragment, "Mime Mapping")) {
1629                 return false;
1630             }
1631         }
1632         mimeMappings.putAll(temp.getMimeMappings());
1633
1634         for (WebXml fragment : fragments) {
1635             if (!mergeResourceMap(fragment.getResourceEnvRefs(), resourceEnvRefs,
1636                     temp.getResourceEnvRefs(), fragment)) {
1637                 return false;
1638             }
1639         }
1640         resourceEnvRefs.putAll(temp.getResourceEnvRefs());
1641
1642         for (WebXml fragment : fragments) {
1643             if (!mergeResourceMap(fragment.getResourceRefs(), resourceRefs,
1644                     temp.getResourceRefs(), fragment)) {
1645                 return false;
1646             }
1647         }
1648         resourceRefs.putAll(temp.getResourceRefs());
1649
1650         for (WebXml fragment : fragments) {
1651             for (SecurityConstraint constraint : fragment.getSecurityConstraints()) {
1652                 // Always additive
1653                 addSecurityConstraint(constraint);
1654             }
1655         }
1656
1657         for (WebXml fragment : fragments) {
1658             for (String role : fragment.getSecurityRoles()) {
1659                 // Always additive
1660                 addSecurityRole(role);
1661             }
1662         }
1663
1664         for (WebXml fragment : fragments) {
1665             if (!mergeResourceMap(fragment.getServiceRefs(), serviceRefs,
1666                     temp.getServiceRefs(), fragment)) {
1667                 return false;
1668             }
1669         }
1670         serviceRefs.putAll(temp.getServiceRefs());
1671
1672         // As per 'clarification' from the Servlet EG, servlet definitions and
1673         // mappings in the main web.xml override those in fragments and those in
1674         // fragments override those in annotations
1675         // Skip servlet definitions and mappings from fragments that are
1676         // defined in web.xml
1677         List<Map.Entry<String,String>> servletMappingsToAdd = new ArrayList<>();
1678         for (WebXml fragment : fragments) {
1679             for (Map.Entry<String,String> servletMap :
1680                     fragment.getServletMappings().entrySet()) {
1681                 if (!servletMappingNames.contains(servletMap.getValue()) &&
1682                         !servletMappings.containsKey(servletMap.getKey())) {
1683                     servletMappingsToAdd.add(servletMap);
1684                 }
1685             }
1686         }
1687
1688         // Add fragment mappings
1689         for (Map.Entry<String,String> mapping : servletMappingsToAdd) {
1690             addServletMappingDecoded(mapping.getKey(), mapping.getValue());
1691         }
1692
1693         for (WebXml fragment : fragments) {
1694             for (Map.Entry<String,ServletDef> entry :
1695                     fragment.getServlets().entrySet()) {
1696                 if (servlets.containsKey(entry.getKey())) {
1697                     mergeServlet(entry.getValue(),
1698                             servlets.get(entry.getKey()), false);
1699                 } else {
1700                     if (temp.getServlets().containsKey(entry.getKey())) {
1701                         if (!(mergeServlet(entry.getValue(),
1702                                 temp.getServlets().get(entry.getKey()), true))) {
1703                             log.error(sm.getString(
1704                                     "webXml.mergeConflictServlet",
1705                                     entry.getKey(),
1706                                     fragment.getName(),
1707                                     fragment.getURL()));
1708
1709                             return false;
1710                         }
1711                     } else {
1712                         temp.getServlets().put(entry.getKey(), entry.getValue());
1713                     }
1714                 }
1715             }
1716         }
1717         servlets.putAll(temp.getServlets());
1718
1719         if (sessionConfig.getSessionTimeout() == null) {
1720             for (WebXml fragment : fragments) {
1721                 Integer value = fragment.getSessionConfig().getSessionTimeout();
1722                 if (value != null) {
1723                     if (temp.getSessionConfig().getSessionTimeout() == null) {
1724                         temp.getSessionConfig().setSessionTimeout(value.toString());
1725                     } else if (value.equals(
1726                             temp.getSessionConfig().getSessionTimeout())) {
1727                         // Fragments use same value - no conflict
1728                     } else {
1729                         log.error(sm.getString(
1730                                 "webXml.mergeConflictSessionTimeout",
1731                                 fragment.getName(),
1732                                 fragment.getURL()));
1733                         return false;
1734                     }
1735                 }
1736             }
1737             if (temp.getSessionConfig().getSessionTimeout() != null) {
1738                 sessionConfig.setSessionTimeout(
1739                         temp.getSessionConfig().getSessionTimeout().toString());
1740             }
1741         }
1742
1743         if (sessionConfig.getCookieName() == null) {
1744             for (WebXml fragment : fragments) {
1745                 String value = fragment.getSessionConfig().getCookieName();
1746                 if (value != null) {
1747                     if (temp.getSessionConfig().getCookieName() == null) {
1748                         temp.getSessionConfig().setCookieName(value);
1749                     } else if (value.equals(
1750                             temp.getSessionConfig().getCookieName())) {
1751                         // Fragments use same value - no conflict
1752                     } else {
1753                         log.error(sm.getString(
1754                                 "webXml.mergeConflictSessionCookieName",
1755                                 fragment.getName(),
1756                                 fragment.getURL()));
1757                         return false;
1758                     }
1759                 }
1760             }
1761             sessionConfig.setCookieName(
1762                     temp.getSessionConfig().getCookieName());
1763         }
1764         if (sessionConfig.getCookieDomain() == null) {
1765             for (WebXml fragment : fragments) {
1766                 String value = fragment.getSessionConfig().getCookieDomain();
1767                 if (value != null) {
1768                     if (temp.getSessionConfig().getCookieDomain() == null) {
1769                         temp.getSessionConfig().setCookieDomain(value);
1770                     } else if (value.equals(
1771                             temp.getSessionConfig().getCookieDomain())) {
1772                         // Fragments use same value - no conflict
1773                     } else {
1774                         log.error(sm.getString(
1775                                 "webXml.mergeConflictSessionCookieDomain",
1776                                 fragment.getName(),
1777                                 fragment.getURL()));
1778                         return false;
1779                     }
1780                 }
1781             }
1782             sessionConfig.setCookieDomain(
1783                     temp.getSessionConfig().getCookieDomain());
1784         }
1785         if (sessionConfig.getCookiePath() == null) {
1786             for (WebXml fragment : fragments) {
1787                 String value = fragment.getSessionConfig().getCookiePath();
1788                 if (value != null) {
1789                     if (temp.getSessionConfig().getCookiePath() == null) {
1790                         temp.getSessionConfig().setCookiePath(value);
1791                     } else if (value.equals(
1792                             temp.getSessionConfig().getCookiePath())) {
1793                         // Fragments use same value - no conflict
1794                     } else {
1795                         log.error(sm.getString(
1796                                 "webXml.mergeConflictSessionCookiePath",
1797                                 fragment.getName(),
1798                                 fragment.getURL()));
1799                         return false;
1800                     }
1801                 }
1802             }
1803             sessionConfig.setCookiePath(
1804                     temp.getSessionConfig().getCookiePath());
1805         }
1806         if (sessionConfig.getCookieComment() == null) {
1807             for (WebXml fragment : fragments) {
1808                 String value = fragment.getSessionConfig().getCookieComment();
1809                 if (value != null) {
1810                     if (temp.getSessionConfig().getCookieComment() == null) {
1811                         temp.getSessionConfig().setCookieComment(value);
1812                     } else if (value.equals(
1813                             temp.getSessionConfig().getCookieComment())) {
1814                         // Fragments use same value - no conflict
1815                     } else {
1816                         log.error(sm.getString(
1817                                 "webXml.mergeConflictSessionCookieComment",
1818                                 fragment.getName(),
1819                                 fragment.getURL()));
1820                         return false;
1821                     }
1822                 }
1823             }
1824             sessionConfig.setCookieComment(
1825                     temp.getSessionConfig().getCookieComment());
1826         }
1827         if (sessionConfig.getCookieHttpOnly() == null) {
1828             for (WebXml fragment : fragments) {
1829                 Boolean value = fragment.getSessionConfig().getCookieHttpOnly();
1830                 if (value != null) {
1831                     if (temp.getSessionConfig().getCookieHttpOnly() == null) {
1832                         temp.getSessionConfig().setCookieHttpOnly(value.toString());
1833                     } else if (value.equals(
1834                             temp.getSessionConfig().getCookieHttpOnly())) {
1835                         // Fragments use same value - no conflict
1836                     } else {
1837                         log.error(sm.getString(
1838                                 "webXml.mergeConflictSessionCookieHttpOnly",
1839                                 fragment.getName(),
1840                                 fragment.getURL()));
1841                         return false;
1842                     }
1843                 }
1844             }
1845             if (temp.getSessionConfig().getCookieHttpOnly() != null) {
1846                 sessionConfig.setCookieHttpOnly(
1847                         temp.getSessionConfig().getCookieHttpOnly().toString());
1848             }
1849         }
1850         if (sessionConfig.getCookieSecure() == null) {
1851             for (WebXml fragment : fragments) {
1852                 Boolean value = fragment.getSessionConfig().getCookieSecure();
1853                 if (value != null) {
1854                     if (temp.getSessionConfig().getCookieSecure() == null) {
1855                         temp.getSessionConfig().setCookieSecure(value.toString());
1856                     } else if (value.equals(
1857                             temp.getSessionConfig().getCookieSecure())) {
1858                         // Fragments use same value - no conflict
1859                     } else {
1860                         log.error(sm.getString(
1861                                 "webXml.mergeConflictSessionCookieSecure",
1862                                 fragment.getName(),
1863                                 fragment.getURL()));
1864                         return false;
1865                     }
1866                 }
1867             }
1868             if (temp.getSessionConfig().getCookieSecure() != null) {
1869                 sessionConfig.setCookieSecure(
1870                         temp.getSessionConfig().getCookieSecure().toString());
1871             }
1872         }
1873         if (sessionConfig.getCookieMaxAge() == null) {
1874             for (WebXml fragment : fragments) {
1875                 Integer value = fragment.getSessionConfig().getCookieMaxAge();
1876                 if (value != null) {
1877                     if (temp.getSessionConfig().getCookieMaxAge() == null) {
1878                         temp.getSessionConfig().setCookieMaxAge(value.toString());
1879                     } else if (value.equals(
1880                             temp.getSessionConfig().getCookieMaxAge())) {
1881                         // Fragments use same value - no conflict
1882                     } else {
1883                         log.error(sm.getString(
1884                                 "webXml.mergeConflictSessionCookieMaxAge",
1885                                 fragment.getName(),
1886                                 fragment.getURL()));
1887                         return false;
1888                     }
1889                 }
1890             }
1891             if (temp.getSessionConfig().getCookieMaxAge() != null) {
1892                 sessionConfig.setCookieMaxAge(
1893                         temp.getSessionConfig().getCookieMaxAge().toString());
1894             }
1895         }
1896
1897         if (sessionConfig.getSessionTrackingModes().size() == 0) {
1898             for (WebXml fragment : fragments) {
1899                 EnumSet<SessionTrackingMode> value =
1900                     fragment.getSessionConfig().getSessionTrackingModes();
1901                 if (value.size() > 0) {
1902                     if (temp.getSessionConfig().getSessionTrackingModes().size() == 0) {
1903                         temp.getSessionConfig().getSessionTrackingModes().addAll(value);
1904                     } else if (value.equals(
1905                             temp.getSessionConfig().getSessionTrackingModes())) {
1906                         // Fragments use same value - no conflict
1907                     } else {
1908                         log.error(sm.getString(
1909                                 "webXml.mergeConflictSessionTrackingMode",
1910                                 fragment.getName(),
1911                                 fragment.getURL()));
1912                         return false;
1913                     }
1914                 }
1915             }
1916             sessionConfig.getSessionTrackingModes().addAll(
1917                     temp.getSessionConfig().getSessionTrackingModes());
1918         }
1919
1920         for (WebXml fragment : fragments) {
1921             if (!mergeMap(fragment.getTaglibs(), taglibs,
1922                     temp.getTaglibs(), fragment, "Taglibs")) {
1923                 return false;
1924             }
1925         }
1926         taglibs.putAll(temp.getTaglibs());
1927
1928         for (WebXml fragment : fragments) {
1929             if (fragment.alwaysAddWelcomeFiles || welcomeFiles.size() == 0) {
1930                 for (String welcomeFile : fragment.getWelcomeFiles()) {
1931                     addWelcomeFile(welcomeFile);
1932                 }
1933             }
1934         }
1935
1936         if (postConstructMethods.isEmpty()) {
1937             for (WebXml fragment : fragments) {
1938                 if (!mergeLifecycleCallback(fragment.getPostConstructMethods(),
1939                         temp.getPostConstructMethods(), fragment,
1940                         "Post Construct Methods")) {
1941                     return false;
1942                 }
1943             }
1944             postConstructMethods.putAll(temp.getPostConstructMethods());
1945         }
1946
1947         if (preDestroyMethods.isEmpty()) {
1948             for (WebXml fragment : fragments) {
1949                 if (!mergeLifecycleCallback(fragment.getPreDestroyMethods(),
1950                         temp.getPreDestroyMethods(), fragment,
1951                         "Pre Destroy Methods")) {
1952                     return false;
1953                 }
1954             }
1955             preDestroyMethods.putAll(temp.getPreDestroyMethods());
1956         }
1957
1958         return true;
1959     }
1960
1961     private <T extends ResourceBase> boolean mergeResourceMap(
1962             Map<String, T> fragmentResources, Map<String, T> mainResources,
1963             Map<String, T> tempResources, WebXml fragment) {
1964         for (T resource : fragmentResources.values()) {
1965             String resourceName = resource.getName();
1966             if (mainResources.containsKey(resourceName)) {
1967                 mainResources.get(resourceName).getInjectionTargets().addAll(
1968                         resource.getInjectionTargets());
1969             } else {
1970                 // Not defined in main web.xml
1971                 T existingResource = tempResources.get(resourceName);
1972                 if (existingResource != null) {
1973                     if (!existingResource.equals(resource)) {
1974                         log.error(sm.getString(
1975                                 "webXml.mergeConflictResource",
1976                                 resourceName,
1977                                 fragment.getName(),
1978                                 fragment.getURL()));
1979                         return false;
1980                     }
1981                 } else {
1982                     tempResources.put(resourceName, resource);
1983                 }
1984             }
1985         }
1986         return true;
1987     }
1988
1989     private <T> boolean mergeMap(Map<String,T> fragmentMap,
1990             Map<String,T> mainMap, Map<String,T> tempMap, WebXml fragment,
1991             String mapName) {
1992         for (Entry<String, T> entry : fragmentMap.entrySet()) {
1993             final String key = entry.getKey();
1994             if (!mainMap.containsKey(key)) {
1995                 // Not defined in main web.xml
1996                 T value = entry.getValue();
1997                 if (tempMap.containsKey(key)) {
1998                     if (value != null && !value.equals(
1999                             tempMap.get(key))) {
2000                         log.error(sm.getString(
2001                                 "webXml.mergeConflictString",
2002                                 mapName,
2003                                 key,
2004                                 fragment.getName(),
2005                                 fragment.getURL()));
2006                         return false;
2007                     }
2008                 } else {
2009                     tempMap.put(key, value);
2010                 }
2011             }
2012         }
2013         return true;
2014     }
2015
2016     private static boolean mergeFilter(FilterDef src, FilterDef dest,
2017             boolean failOnConflict) {
2018         if (dest.getAsyncSupported() == null) {
2019             dest.setAsyncSupported(src.getAsyncSupported());
2020         } else if (src.getAsyncSupported() != null) {
2021             if (failOnConflict &&
2022                     !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
2023                 return false;
2024             }
2025         }
2026
2027         if (dest.getFilterClass()  == null) {
2028             dest.setFilterClass(src.getFilterClass());
2029         } else if (src.getFilterClass() != null) {
2030             if (failOnConflict &&
2031                     !src.getFilterClass().equals(dest.getFilterClass())) {
2032                 return false;
2033             }
2034         }
2035
2036         for (Map.Entry<String,String> srcEntry :
2037                 src.getParameterMap().entrySet()) {
2038             if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
2039                 if (failOnConflict && !dest.getParameterMap().get(
2040                         srcEntry.getKey()).equals(srcEntry.getValue())) {
2041                     return false;
2042                 }
2043             } else {
2044                 dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
2045             }
2046         }
2047         return true;
2048     }
2049
2050     private static boolean mergeServlet(ServletDef src, ServletDef dest,
2051             boolean failOnConflict) {
2052         // These tests should be unnecessary...
2053         if (dest.getServletClass() != null && dest.getJspFile() != null) {
2054             return false;
2055         }
2056         if (src.getServletClass() != null && src.getJspFile() != null) {
2057             return false;
2058         }
2059
2060
2061         if (dest.getServletClass() == null && dest.getJspFile() == null) {
2062             dest.setServletClass(src.getServletClass());
2063             dest.setJspFile(src.getJspFile());
2064         } else if (failOnConflict) {
2065             if (src.getServletClass() != null &&
2066                     (dest.getJspFile() != null ||
2067                             !src.getServletClass().equals(dest.getServletClass()))) {
2068                 return false;
2069             }
2070             if (src.getJspFile() != null &&
2071                     (dest.getServletClass() != null ||
2072                             !src.getJspFile().equals(dest.getJspFile()))) {
2073                 return false;
2074             }
2075         }
2076
2077         // Additive
2078         for (SecurityRoleRef securityRoleRef : src.getSecurityRoleRefs()) {
2079             dest.addSecurityRoleRef(securityRoleRef);
2080         }
2081
2082         if (dest.getLoadOnStartup() == null) {
2083             if (src.getLoadOnStartup() != null) {
2084                 dest.setLoadOnStartup(src.getLoadOnStartup().toString());
2085             }
2086         } else if (src.getLoadOnStartup() != null) {
2087             if (failOnConflict &&
2088                     !src.getLoadOnStartup().equals(dest.getLoadOnStartup())) {
2089                 return false;
2090             }
2091         }
2092
2093         if (dest.getEnabled() == null) {
2094             if (src.getEnabled() != null) {
2095                 dest.setEnabled(src.getEnabled().toString());
2096             }
2097         } else if (src.getEnabled() != null) {
2098             if (failOnConflict &&
2099                     !src.getEnabled().equals(dest.getEnabled())) {
2100                 return false;
2101             }
2102         }
2103
2104         for (Map.Entry<String,String> srcEntry :
2105                 src.getParameterMap().entrySet()) {
2106             if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
2107                 if (failOnConflict && !dest.getParameterMap().get(
2108                         srcEntry.getKey()).equals(srcEntry.getValue())) {
2109                     return false;
2110                 }
2111             } else {
2112                 dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
2113             }
2114         }
2115
2116         if (dest.getMultipartDef() == null) {
2117             dest.setMultipartDef(src.getMultipartDef());
2118         } else if (src.getMultipartDef() != null) {
2119             return mergeMultipartDef(src.getMultipartDef(),
2120                     dest.getMultipartDef(), failOnConflict);
2121         }
2122
2123         if (dest.getAsyncSupported() == null) {
2124             if (src.getAsyncSupported() != null) {
2125                 dest.setAsyncSupported(src.getAsyncSupported().toString());
2126             }
2127         } else if (src.getAsyncSupported() != null) {
2128             if (failOnConflict &&
2129                     !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
2130                 return false;
2131             }
2132         }
2133
2134         return true;
2135     }
2136
2137     private static boolean mergeMultipartDef(MultipartDef src, MultipartDef dest,
2138             boolean failOnConflict) {
2139
2140         if (dest.getLocation() == null) {
2141             dest.setLocation(src.getLocation());
2142         } else if (src.getLocation() != null) {
2143             if (failOnConflict &&
2144                     !src.getLocation().equals(dest.getLocation())) {
2145                 return false;
2146             }
2147         }
2148
2149         if (dest.getFileSizeThreshold() == null) {
2150             dest.setFileSizeThreshold(src.getFileSizeThreshold());
2151         } else if (src.getFileSizeThreshold() != null) {
2152             if (failOnConflict &&
2153                     !src.getFileSizeThreshold().equals(
2154                             dest.getFileSizeThreshold())) {
2155                 return false;
2156             }
2157         }
2158
2159         if (dest.getMaxFileSize() == null) {
2160             dest.setMaxFileSize(src.getMaxFileSize());
2161         } else if (src.getMaxFileSize() != null) {
2162             if (failOnConflict &&
2163                     !src.getMaxFileSize().equals(dest.getMaxFileSize())) {
2164                 return false;
2165             }
2166         }
2167
2168         if (dest.getMaxRequestSize() == null) {
2169             dest.setMaxRequestSize(src.getMaxRequestSize());
2170         } else if (src.getMaxRequestSize() != null) {
2171             if (failOnConflict &&
2172                     !src.getMaxRequestSize().equals(
2173                             dest.getMaxRequestSize())) {
2174                 return false;
2175             }
2176         }
2177
2178         return true;
2179     }
2180
2181
2182     private boolean mergeLifecycleCallback(
2183             Map<String, String> fragmentMap, Map<String, String> tempMap,
2184             WebXml fragment, String mapName) {
2185         for (Entry<String, String> entry : fragmentMap.entrySet()) {
2186             final String key = entry.getKey();
2187             final String value = entry.getValue();
2188             if (tempMap.containsKey(key)) {
2189                 if (value != null && !value.equals(tempMap.get(key))) {
2190                     log.error(sm.getString("webXml.mergeConflictString",
2191                             mapName, key, fragment.getName(), fragment.getURL()));
2192                     return false;
2193                 }
2194             } else {
2195                 tempMap.put(key, value);
2196             }
2197         }
2198         return true;
2199     }
2200
2201
2202     /**
2203      * Generates the sub-set of the web-fragment.xml files to be processed in
2204      * the order that the fragments must be processed as per the rules in the
2205      * Servlet spec.
2206      *
2207      * @param application    The application web.xml file
2208      * @param fragments      The map of fragment names to web fragments
2209      * @param servletContext The servlet context the fragments are associated
2210      *                       with
2211      * @return Ordered list of web-fragment.xml files to process
2212      */

2213     public static Set<WebXml> orderWebFragments(WebXml application,
2214             Map<String,WebXml> fragments, ServletContext servletContext) {
2215         return application.orderWebFragments(fragments, servletContext);
2216     }
2217
2218
2219     private Set<WebXml> orderWebFragments(Map<String,WebXml> fragments,
2220             ServletContext servletContext) {
2221
2222         Set<WebXml> orderedFragments = new LinkedHashSet<>();
2223
2224         boolean absoluteOrdering = getAbsoluteOrdering() != null;
2225         boolean orderingPresent = false;
2226
2227         if (absoluteOrdering) {
2228             orderingPresent = true;
2229             // Only those fragments listed should be processed
2230             Set<String> requestedOrder = getAbsoluteOrdering();
2231
2232             for (String requestedName : requestedOrder) {
2233                 if (WebXml.ORDER_OTHERS.equals(requestedName)) {
2234                     // Add all fragments not named explicitly at this point
2235                     for (Entry<String, WebXml> entry : fragments.entrySet()) {
2236                         if (!requestedOrder.contains(entry.getKey())) {
2237                             WebXml fragment = entry.getValue();
2238                             if (fragment != null) {
2239                                 orderedFragments.add(fragment);
2240                             }
2241                         }
2242                     }
2243                 } else {
2244                     WebXml fragment = fragments.get(requestedName);
2245                     if (fragment != null) {
2246                         orderedFragments.add(fragment);
2247                     } else {
2248                         log.warn(sm.getString("webXml.wrongFragmentName",requestedName));
2249                     }
2250                 }
2251             }
2252         } else {
2253             // Stage 0. Check there were no fragments with duplicate names
2254             for (WebXml fragment : fragments.values()) {
2255                 if (fragment.isDuplicated()) {
2256                     throw new IllegalArgumentException(
2257                             sm.getString("webXml.duplicateFragment", fragment.getName()));
2258                 }
2259             }
2260             // Stage 1. Make all dependencies bi-directional - this makes the
2261             //          next stage simpler.
2262             for (WebXml fragment : fragments.values()) {
2263                 Iterator<String> before =
2264                         fragment.getBeforeOrdering().iterator();
2265                 while (before.hasNext()) {
2266                     orderingPresent = true;
2267                     String beforeEntry = before.next();
2268                     if (!beforeEntry.equals(ORDER_OTHERS)) {
2269                         WebXml beforeFragment = fragments.get(beforeEntry);
2270                         if (beforeFragment == null) {
2271                             before.remove();
2272                         } else {
2273                             beforeFragment.addAfterOrdering(fragment.getName());
2274                         }
2275                     }
2276                 }
2277                 Iterator<String> after = fragment.getAfterOrdering().iterator();
2278                 while (after.hasNext()) {
2279                     orderingPresent = true;
2280                     String afterEntry = after.next();
2281                     if (!afterEntry.equals(ORDER_OTHERS)) {
2282                         WebXml afterFragment = fragments.get(afterEntry);
2283                         if (afterFragment == null) {
2284                             after.remove();
2285                         } else {
2286                             afterFragment.addBeforeOrdering(fragment.getName());
2287                         }
2288                     }
2289                 }
2290             }
2291
2292             // Stage 2. Make all fragments that are implicitly before/after
2293             //          others explicitly so. This is iterative so the next
2294             //          stage doesn't have to be.
2295             for (WebXml fragment : fragments.values()) {
2296                 if (fragment.getBeforeOrdering().contains(ORDER_OTHERS)) {
2297                     makeBeforeOthersExplicit(fragment.getAfterOrdering(), fragments);
2298                 }
2299                 if (fragment.getAfterOrdering().contains(ORDER_OTHERS)) {
2300                     makeAfterOthersExplicit(fragment.getBeforeOrdering(), fragments);
2301                 }
2302             }
2303
2304             // Stage 3. Separate into three groups
2305             Set<WebXml> beforeSet = new HashSet<>();
2306             Set<WebXml> othersSet = new HashSet<>();
2307             Set<WebXml> afterSet = new HashSet<>();
2308
2309             for (WebXml fragment : fragments.values()) {
2310                 if (fragment.getBeforeOrdering().contains(ORDER_OTHERS)) {
2311                     beforeSet.add(fragment);
2312                     fragment.getBeforeOrdering().remove(ORDER_OTHERS);
2313                 } else if (fragment.getAfterOrdering().contains(ORDER_OTHERS)) {
2314                     afterSet.add(fragment);
2315                     fragment.getAfterOrdering().remove(ORDER_OTHERS);
2316                 } else {
2317                     othersSet.add(fragment);
2318                 }
2319             }
2320
2321             // Stage 4. Decouple the groups so the ordering requirements for
2322             //          each fragment in the group only refer to other fragments
2323             //          in the group. Ordering requirements outside the group
2324             //          will be handled by processing the groups in order.
2325             //          Note: Only after ordering requirements are considered.
2326             //                This is OK because of the processing in stage 1.
2327             decoupleOtherGroups(beforeSet);
2328             decoupleOtherGroups(othersSet);
2329             decoupleOtherGroups(afterSet);
2330
2331             // Stage 5. Order each group
2332             //          Note: Only after ordering requirements are considered.
2333             //                This is OK because of the processing in stage 1.
2334             orderFragments(orderedFragments, beforeSet);
2335             orderFragments(orderedFragments, othersSet);
2336             orderFragments(orderedFragments, afterSet);
2337         }
2338
2339         // Container fragments are always included
2340         Set<WebXml> containerFragments = new LinkedHashSet<>();
2341         // Find all the container fragments and remove any present from the
2342         // ordered list
2343         for (WebXml fragment : fragments.values()) {
2344             if (!fragment.getWebappJar()) {
2345                 containerFragments.add(fragment);
2346                 orderedFragments.remove(fragment);
2347             }
2348         }
2349
2350         // Avoid NPE when unit testing
2351         if (servletContext != null) {
2352             // Publish the ordered fragments. The app does not need to know
2353             // about container fragments
2354             List<String> orderedJarFileNames = null;
2355             if (orderingPresent) {
2356                 orderedJarFileNames = new ArrayList<>();
2357                 for (WebXml fragment: orderedFragments) {
2358                     orderedJarFileNames.add(fragment.getJarName());
2359                 }
2360             }
2361             servletContext.setAttribute(ServletContext.ORDERED_LIBS,
2362                     orderedJarFileNames);
2363         }
2364
2365         // The remainder of the processing needs to know about container
2366         // fragments
2367         if (containerFragments.size() > 0) {
2368             Set<WebXml> result = new LinkedHashSet<>();
2369             if (containerFragments.iterator().next().getDelegate()) {
2370                 result.addAll(containerFragments);
2371                 result.addAll(orderedFragments);
2372             } else {
2373                 result.addAll(orderedFragments);
2374                 result.addAll(containerFragments);
2375             }
2376             return result;
2377         } else {
2378             return orderedFragments;
2379         }
2380     }
2381
2382     private static void decoupleOtherGroups(Set<WebXml> group) {
2383         Set<String> names = new HashSet<>();
2384         for (WebXml fragment : group) {
2385             names.add(fragment.getName());
2386         }
2387         for (WebXml fragment : group) {
2388             Iterator<String> after = fragment.getAfterOrdering().iterator();
2389             while (after.hasNext()) {
2390                 String entry = after.next();
2391                 if (!names.contains(entry)) {
2392                     after.remove();
2393                 }
2394             }
2395         }
2396     }
2397     private static void orderFragments(Set<WebXml> orderedFragments,
2398             Set<WebXml> unordered) {
2399         Set<WebXml> addedThisRound = new HashSet<>();
2400         Set<WebXml> addedLastRound = new HashSet<>();
2401         while (unordered.size() > 0) {
2402             Iterator<WebXml> source = unordered.iterator();
2403             while (source.hasNext()) {
2404                 WebXml fragment = source.next();
2405                 for (WebXml toRemove : addedLastRound) {
2406                     fragment.getAfterOrdering().remove(toRemove.getName());
2407                 }
2408                 if (fragment.getAfterOrdering().isEmpty()) {
2409                     addedThisRound.add(fragment);
2410                     orderedFragments.add(fragment);
2411                     source.remove();
2412                 }
2413             }
2414             if (addedThisRound.size() == 0) {
2415                 // Circular
2416                 throw new IllegalArgumentException(
2417                         sm.getString("webXml.mergeConflictOrder"));
2418             }
2419             addedLastRound.clear();
2420             addedLastRound.addAll(addedThisRound);
2421             addedThisRound.clear();
2422         }
2423     }
2424
2425     private static void makeBeforeOthersExplicit(Set<String> beforeOrdering,
2426             Map<String, WebXml> fragments) {
2427         for (String before : beforeOrdering) {
2428             if (!before.equals(ORDER_OTHERS)) {
2429                 WebXml webXml = fragments.get(before);
2430                 if (!webXml.getBeforeOrdering().contains(ORDER_OTHERS)) {
2431                     webXml.addBeforeOrderingOthers();
2432                     makeBeforeOthersExplicit(webXml.getAfterOrdering(), fragments);
2433                 }
2434             }
2435         }
2436     }
2437
2438     private static void makeAfterOthersExplicit(Set<String> afterOrdering,
2439             Map<String, WebXml> fragments) {
2440         for (String after : afterOrdering) {
2441             if (!after.equals(ORDER_OTHERS)) {
2442                 WebXml webXml = fragments.get(after);
2443                 if (!webXml.getAfterOrdering().contains(ORDER_OTHERS)) {
2444                     webXml.addAfterOrderingOthers();
2445                     makeAfterOthersExplicit(webXml.getBeforeOrdering(), fragments);
2446                 }
2447             }
2448         }
2449     }
2450 }
2451