View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  
20  package org.eclipse.jetty.quickstart;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.net.URL;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.EventListener;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import javax.servlet.DispatcherType;
36  import javax.servlet.MultipartConfigElement;
37  import javax.servlet.ServletContext;
38  import javax.servlet.SessionCookieConfig;
39  import javax.servlet.SessionTrackingMode;
40  import javax.servlet.descriptor.JspPropertyGroupDescriptor;
41  import javax.servlet.descriptor.TaglibDescriptor;
42  
43  import org.eclipse.jetty.annotations.AnnotationConfiguration;
44  import org.eclipse.jetty.http.MimeTypes;
45  import org.eclipse.jetty.plus.annotation.LifeCycleCallback;
46  import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
47  import org.eclipse.jetty.security.ConstraintAware;
48  import org.eclipse.jetty.security.ConstraintMapping;
49  import org.eclipse.jetty.security.SecurityHandler;
50  import org.eclipse.jetty.security.authentication.FormAuthenticator;
51  import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
52  import org.eclipse.jetty.servlet.FilterHolder;
53  import org.eclipse.jetty.servlet.FilterMapping;
54  import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
55  import org.eclipse.jetty.servlet.ServletHandler;
56  import org.eclipse.jetty.servlet.ServletHolder;
57  import org.eclipse.jetty.servlet.ServletMapping;
58  import org.eclipse.jetty.util.QuotedStringTokenizer;
59  import org.eclipse.jetty.util.log.Log;
60  import org.eclipse.jetty.util.log.Logger;
61  import org.eclipse.jetty.util.resource.Resource;
62  import org.eclipse.jetty.util.security.Constraint;
63  import org.eclipse.jetty.webapp.MetaData;
64  import org.eclipse.jetty.webapp.MetaData.OriginInfo;
65  import org.eclipse.jetty.webapp.MetaInfConfiguration;
66  import org.eclipse.jetty.webapp.WebAppContext;
67  import org.eclipse.jetty.xml.XmlAppendable;
68  
69  /**
70   * QuickStartDescriptorGenerator
71   * <p>
72   * Generate an effective web.xml from a WebAppContext, including all components 
73   * from web.xml, web-fragment.xmls annotations etc.
74   */
75  public class QuickStartDescriptorGenerator
76  {
77      private static final Logger LOG = Log.getLogger(QuickStartDescriptorGenerator.class);
78      
79      public static final String DEFAULT_QUICKSTART_DESCRIPTOR_NAME = "quickstart-web.xml";
80      
81      protected WebAppContext _webApp;
82      protected String _extraXML;
83    
84      
85      
86      /**
87       * @param w the source WebAppContext
88       * @param extraXML any extra xml snippet to append
89       */
90      public QuickStartDescriptorGenerator (WebAppContext w,  String extraXML)
91      {
92          _webApp = w;
93          _extraXML = extraXML;
94      }
95      
96      
97      /**
98       * Perform the generation of the xml file
99       * @param stream the stream to generate the quickstart-web.xml to
100      * @throws IOException if unable to generate the quickstart-web.xml
101      * @throws FileNotFoundException if unable to find the file 
102      */
103     public void generateQuickStartWebXml (OutputStream stream) throws FileNotFoundException, IOException 
104     {   
105         if (_webApp == null)
106             throw new IllegalStateException("No webapp for quickstart generation");
107         if (stream == null)
108             throw new IllegalStateException("No output for quickstart generation");
109         
110         _webApp.getMetaData().getOrigins();
111 
112         if (_webApp.getBaseResource()==null)
113             throw new IllegalArgumentException("No base resource for "+this);
114 
115         LOG.info("Quickstart generating");
116 
117         XmlAppendable out = new XmlAppendable(stream,"UTF-8");
118 
119         MetaData md = _webApp.getMetaData();
120 
121         Map<String, String> webappAttr = new HashMap<>();
122         webappAttr.put("xmlns","http://xmlns.jcp.org/xml/ns/javaee");
123         webappAttr.put("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
124         webappAttr.put("xsi:schemaLocation","http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd");
125         webappAttr.put("metadata-complete","true");
126         webappAttr.put("version","3.1");
127 
128         out.openTag("web-app",webappAttr);
129         if (_webApp.getDisplayName() != null)
130             out.tag("display-name",_webApp.getDisplayName());
131         
132         // Set some special context parameters
133 
134         // The location of the war file on disk
135         AttributeNormalizer normalizer = new AttributeNormalizer(_webApp.getBaseResource());
136 
137         // The library order
138         addContextParamFromAttribute(out,ServletContext.ORDERED_LIBS);
139         //the servlet container initializers
140         addContextParamFromAttribute(out,AnnotationConfiguration.CONTAINER_INITIALIZERS);
141         //the tlds discovered
142         addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_TLDS,normalizer);
143         //the META-INF/resources discovered
144         addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_RESOURCES,normalizer);
145 
146 
147         // init params
148         for (String p : _webApp.getInitParams().keySet())
149             out.openTag("context-param",origin(md,"context-param." + p))
150             .tag("param-name",p)
151             .tag("param-value",_webApp.getInitParameter(p))
152             .closeTag();
153 
154         if (_webApp.getEventListeners() != null)
155             for (EventListener e : _webApp.getEventListeners())
156                 out.openTag("listener",origin(md,e.getClass().getCanonicalName() + ".listener"))
157                 .tag("listener-class",e.getClass().getCanonicalName())
158                 .closeTag();
159 
160         ServletHandler servlets = _webApp.getServletHandler();
161 
162         if (servlets.getFilters() != null)
163         {
164             for (FilterHolder holder : servlets.getFilters())
165                 outholder(out,md,holder);
166         }
167 
168         if (servlets.getFilterMappings() != null)
169         {
170             for (FilterMapping mapping : servlets.getFilterMappings())
171             {
172                 out.openTag("filter-mapping");
173                 out.tag("filter-name",mapping.getFilterName());
174                 if (mapping.getPathSpecs() != null)
175                     for (String s : mapping.getPathSpecs())
176                         out.tag("url-pattern",s);
177                 if (mapping.getServletNames() != null)
178                     for (String n : mapping.getServletNames())
179                         out.tag("servlet-name",n);
180 
181                 if (!mapping.isDefaultDispatches())
182                 {
183                     if (mapping.appliesTo(DispatcherType.REQUEST))
184                         out.tag("dispatcher","REQUEST");
185                     if (mapping.appliesTo(DispatcherType.ASYNC))
186                         out.tag("dispatcher","ASYNC");
187                     if (mapping.appliesTo(DispatcherType.ERROR))
188                         out.tag("dispatcher","ERROR");
189                     if (mapping.appliesTo(DispatcherType.FORWARD))
190                         out.tag("dispatcher","FORWARD");
191                     if (mapping.appliesTo(DispatcherType.INCLUDE))
192                         out.tag("dispatcher","INCLUDE");
193                 }
194                 out.closeTag();
195             }
196         }
197 
198         if (servlets.getServlets() != null)
199         {
200             for (ServletHolder holder : servlets.getServlets())
201                 outholder(out,md,holder);
202         }
203 
204         if (servlets.getServletMappings() != null)
205         {
206             for (ServletMapping mapping : servlets.getServletMappings())
207             {
208                 out.openTag("servlet-mapping",origin(md,mapping.getServletName() + ".servlet.mappings"));
209                 out.tag("servlet-name",mapping.getServletName());
210                 if (mapping.getPathSpecs() != null)
211                     for (String s : mapping.getPathSpecs())
212                         out.tag("url-pattern",s);
213                 out.closeTag();
214             }
215         }
216 
217         // Security elements
218         SecurityHandler security =_webApp. getSecurityHandler();
219 
220         if (security!=null && (security.getRealmName()!=null || security.getAuthMethod()!=null))
221         {
222             out.openTag("login-config");
223             if (security.getAuthMethod()!=null)
224                 out.tag("auth-method",origin(md,"auth-method"),security.getAuthMethod());
225             if (security.getRealmName()!=null)
226                 out.tag("realm-name",origin(md,"realm-name"),security.getRealmName());
227 
228 
229             if (Constraint.__FORM_AUTH.equalsIgnoreCase(security.getAuthMethod()))
230             {
231                 out.openTag("form-login-config");
232                 out.tag("form-login-page",origin(md,"form-login-page"),security.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE));
233                 out.tag("form-error-page",origin(md,"form-error-page"),security.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE));
234                 out.closeTag();
235             }
236 
237             out.closeTag();
238         }
239 
240         if (security instanceof ConstraintAware)
241         {
242             ConstraintAware ca = (ConstraintAware)security;
243             for (String r:ca.getRoles())
244                 out.openTag("security-role")
245                 .tag("role-name",r)
246                 .closeTag();
247 
248             for (ConstraintMapping m : ca.getConstraintMappings())
249             {
250                 out.openTag("security-constraint");
251 
252                 out.openTag("web-resource-collection");
253                 {
254                     if (m.getConstraint().getName()!=null)
255                         out.tag("web-resource-name",m.getConstraint().getName());
256                     if (m.getPathSpec()!=null)
257                         out.tag("url-pattern",origin(md,"constraint.url."+m.getPathSpec()),m.getPathSpec());
258                     if (m.getMethod()!=null)
259                         out.tag("http-method",m.getMethod());
260 
261                     if (m.getMethodOmissions()!=null)
262                         for (String o:m.getMethodOmissions())
263                             out.tag("http-method-omission",o);
264 
265                     out.closeTag();
266                 }
267 
268                 if (m.getConstraint().getAuthenticate())
269                 {
270                     String[] roles = m.getConstraint().getRoles();
271                     if (roles!=null && roles.length>0)
272                     {
273                         out.openTag("auth-constraint");
274                         if (m.getConstraint().getRoles()!=null)
275                             for (String r : m.getConstraint().getRoles())
276                                 out.tag("role-name",r);
277                         out.closeTag();
278                     }
279                     else
280                         out.tag("auth-constraint");
281                 }
282 
283                 switch (m.getConstraint().getDataConstraint())
284                 {
285                     case Constraint.DC_NONE:
286                         out.openTag("user-data-constraint").tag("transport-guarantee","NONE").closeTag();
287                         break;
288 
289                     case Constraint.DC_INTEGRAL:
290                         out.openTag("user-data-constraint").tag("transport-guarantee","INTEGRAL").closeTag();
291                         break;
292 
293                     case Constraint.DC_CONFIDENTIAL:
294                         out.openTag("user-data-constraint").tag("transport-guarantee","CONFIDENTIAL").closeTag();
295                         break;
296 
297                     default:
298                         break;
299 
300                 }
301 
302                 out.closeTag();
303 
304             }
305         }
306 
307         if (_webApp.getWelcomeFiles() != null)
308         {
309             out.openTag("welcome-file-list");
310             for (String welcomeFile:_webApp.getWelcomeFiles())
311             {
312                 out.tag("welcome-file", welcomeFile);
313             }
314             out.closeTag();
315         }
316 
317         Map<String,String> localeEncodings = _webApp.getLocaleEncodings();
318         if (localeEncodings != null && !localeEncodings.isEmpty())
319         {
320             out.openTag("locale-encoding-mapping-list");
321             for (Map.Entry<String, String> entry:localeEncodings.entrySet())
322             {
323                 out.openTag("locale-encoding-mapping", origin(md,"locale-encoding."+entry.getKey()));
324                 out.tag("locale", entry.getKey());
325                 out.tag("encoding", entry.getValue());
326                 out.closeTag();
327             }
328             out.closeTag();
329         }
330 
331         //session-config
332         if (_webApp.getSessionHandler().getSessionManager() != null)
333         {
334             out.openTag("session-config");
335             int maxInactiveSec = _webApp.getSessionHandler().getSessionManager().getMaxInactiveInterval();
336             out.tag("session-timeout", (maxInactiveSec==0?"0":Integer.toString(maxInactiveSec/60)));
337 
338 
339             //cookie-config
340             SessionCookieConfig cookieConfig = _webApp.getSessionHandler().getSessionManager().getSessionCookieConfig();
341             if (cookieConfig != null)
342             {
343                 out.openTag("cookie-config");
344                 if (cookieConfig.getName() != null)
345                     out.tag("name", origin(md,"cookie-config.name"), cookieConfig.getName());
346 
347                 if (cookieConfig.getDomain() != null)
348                     out.tag("domain", origin(md, "cookie-config.domain"), cookieConfig.getDomain());
349 
350                 if (cookieConfig.getPath() != null)
351                     out.tag("path", origin(md, "cookie-config.path"), cookieConfig.getPath());
352 
353                 if (cookieConfig.getComment() != null)
354                     out.tag("comment", origin(md, "cookie-config.comment"), cookieConfig.getComment());
355 
356                 out.tag("http-only", origin(md, "cookie-config.http-only"), Boolean.toString(cookieConfig.isHttpOnly()));
357                 out.tag("secure", origin(md, "cookie-config.secure"), Boolean.toString(cookieConfig.isSecure()));
358                 out.tag("max-age", origin(md, "cookie-config.max-age"), Integer.toString(cookieConfig.getMaxAge()));
359                 out.closeTag();
360             }
361             
362             // tracking-modes
363             Set<SessionTrackingMode> modes =_webApp. getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes();
364             if (modes != null)
365             {
366                 for (SessionTrackingMode mode:modes)
367                     out.tag("tracking-mode", mode.toString());
368             }
369             
370             out.closeTag();     
371         }
372 
373         //error-pages
374         Map<String,String> errorPages = ((ErrorPageErrorHandler)_webApp.getErrorHandler()).getErrorPages();
375         if (errorPages != null)
376         {
377             for (Map.Entry<String, String> entry:errorPages.entrySet())
378             {
379                 out.openTag("error-page", origin(md, "error."+entry.getKey()));
380                 //a global or default error page has no code or exception               
381                 if (!ErrorPageErrorHandler.GLOBAL_ERROR_PAGE.equals(entry.getKey()))
382                 {
383                     if (entry.getKey().matches("\\d{3}"))
384                         out.tag("error-code", entry.getKey());
385                     else
386                         out.tag("exception-type", entry.getKey());
387                 }
388                 out.tag("location", entry.getValue());
389                 out.closeTag();
390             }
391         }
392 
393         //mime-types
394         MimeTypes mimeTypes = _webApp.getMimeTypes();
395         if (mimeTypes != null)
396         {
397             for (Map.Entry<String, String> entry:mimeTypes.getMimeMap().entrySet())
398             {
399                 out.openTag("mime-mapping");
400                 out.tag("extension", origin(md, "extension."+entry.getKey()), entry.getKey());
401                 out.tag("mime-type", entry.getValue());
402                 out.closeTag();
403             }
404         }
405 
406         //jsp-config
407         JspConfig jspConfig = (JspConfig)_webApp.getServletContext().getJspConfigDescriptor();
408         if (jspConfig != null)
409         {
410             out.openTag("jsp-config");
411             Collection<TaglibDescriptor> tlds = jspConfig.getTaglibs();
412             if (tlds != null && !tlds.isEmpty())
413             {
414                 for (TaglibDescriptor tld:tlds)
415                 {
416                     out.openTag("taglib");
417                     out.tag("taglib-uri", tld.getTaglibURI());
418                     out.tag("taglib-location", tld.getTaglibLocation());
419                     out.closeTag();
420                 }
421             }
422 
423             Collection<JspPropertyGroupDescriptor> jspPropertyGroups = jspConfig.getJspPropertyGroups();
424             if (jspPropertyGroups != null && !jspPropertyGroups.isEmpty())
425             {
426                 for (JspPropertyGroupDescriptor jspPropertyGroup:jspPropertyGroups)
427                 {
428                     out.openTag("jsp-property-group");
429                     Collection<String> strings = jspPropertyGroup.getUrlPatterns();
430                     if (strings != null && !strings.isEmpty())
431                     {
432                         for (String urlPattern:strings)
433                             out.tag("url-pattern", urlPattern);
434                     }
435 
436                     if (jspPropertyGroup.getElIgnored() != null)
437                         out.tag("el-ignored", jspPropertyGroup.getElIgnored());
438 
439                     if (jspPropertyGroup.getPageEncoding() != null)
440                         out.tag("page-encoding", jspPropertyGroup.getPageEncoding());
441 
442                     if (jspPropertyGroup.getScriptingInvalid() != null)
443                         out.tag("scripting-invalid", jspPropertyGroup.getScriptingInvalid());
444 
445                     if (jspPropertyGroup.getIsXml() != null)
446                         out.tag("is-xml", jspPropertyGroup.getIsXml());
447 
448                     if (jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral() != null)
449                         out.tag("deferred-syntax-allowed-as-literal", jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral());
450 
451                     if (jspPropertyGroup.getTrimDirectiveWhitespaces() != null)
452                         out.tag("trim-directive-whitespaces", jspPropertyGroup.getTrimDirectiveWhitespaces());
453 
454                     if (jspPropertyGroup.getDefaultContentType() != null)
455                         out.tag("default-content-type", jspPropertyGroup.getDefaultContentType());
456 
457                     if (jspPropertyGroup.getBuffer() != null)
458                         out.tag("buffer", jspPropertyGroup.getBuffer());
459 
460                     if (jspPropertyGroup.getErrorOnUndeclaredNamespace() != null)
461                         out.tag("error-on-undeclared-namespace", jspPropertyGroup.getErrorOnUndeclaredNamespace());
462 
463                     strings = jspPropertyGroup.getIncludePreludes();
464                     if (strings != null && !strings.isEmpty())
465                     {
466                         for (String prelude:strings)
467                             out.tag("include-prelude", prelude);
468                     }
469 
470                     strings = jspPropertyGroup.getIncludeCodas();
471                     if (strings != null && !strings.isEmpty())
472                     {
473                         for (String coda:strings)
474                             out.tag("include-coda", coda);
475                     }
476 
477                     out.closeTag();
478                 }
479             }
480 
481             out.closeTag();
482         }
483 
484         //lifecycle: post-construct, pre-destroy
485         LifeCycleCallbackCollection lifecycles = ((LifeCycleCallbackCollection)_webApp.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION));
486         if (lifecycles != null)
487         {
488             Collection<LifeCycleCallback> tmp = lifecycles.getPostConstructCallbacks();
489 
490             for (LifeCycleCallback c:tmp)
491             {
492                 out.openTag("post-construct");
493                 out.tag("lifecycle-callback-class", c.getTargetClassName());
494                 out.tag("lifecycle-callback-method", c.getMethodName());
495                 out.closeTag();
496             }
497 
498             tmp = lifecycles.getPreDestroyCallbacks();
499             for (LifeCycleCallback c:tmp)
500             {
501                 out.openTag("pre-destroy");
502                 out.tag("lifecycle-callback-class", c.getTargetClassName());
503                 out.tag("lifecycle-callback-method", c.getMethodName());
504                 out.closeTag();
505             }
506         }
507 
508         out.literal(_extraXML);
509 
510         out.closeTag();
511     }
512 
513     /**
514      * Turn attribute into context-param to store.
515      * 
516      * @param out
517      * @param attribute
518      * @throws IOException
519      */
520     private void addContextParamFromAttribute(XmlAppendable out, String attribute) throws IOException
521     {
522         Object o = _webApp.getAttribute(attribute);
523         if (o == null)
524             return;
525                 
526         Collection<?> c =  (o instanceof Collection)? (Collection<?>)o:Collections.singletonList(o);
527         StringBuilder v=new StringBuilder();
528         for (Object i:c)
529         {
530             if (i!=null)
531             {
532                 if (v.length()>0)
533                     v.append(",\n    ");
534                 else
535                     v.append("\n    ");
536                 QuotedStringTokenizer.quote(v,i.toString());
537             }
538         }
539         out.openTag("context-param")
540         .tag("param-name",attribute)
541         .tagCDATA("param-value",v.toString())
542         .closeTag();        
543     }
544     
545     /**
546      * Turn context attribute into context-param to store.
547      * 
548      * @param out
549      * @param attribute
550      * @param resourceBase
551      * @throws IOException
552      */
553     private void addContextParamFromAttribute(XmlAppendable out, String attribute, AttributeNormalizer normalizer) throws IOException
554     {
555         Object o = _webApp.getAttribute(attribute);
556         if (o == null)
557             return;
558                 
559         Collection<?> c =  (o instanceof Collection)? (Collection<?>)o:Collections.singletonList(o);
560         StringBuilder v=new StringBuilder();
561         for (Object i:c)
562         {
563             if (i!=null)
564             {
565                 if (v.length()>0)
566                     v.append(",\n    ");
567                 else
568                     v.append("\n    ");
569                 QuotedStringTokenizer.quote(v,normalizer.normalize(i));
570             }
571         }
572         out.openTag("context-param")
573         .tag("param-name",attribute)
574         .tagCDATA("param-value",v.toString())
575         .closeTag();  
576         
577     }
578 
579     /**
580      * Generate xml for a Holder (Filter/Servlet)
581      * 
582      * @param out
583      * @param md
584      * @param tag
585      * @param holder
586      * @throws IOException
587      */
588     private void outholder(XmlAppendable out, MetaData md, FilterHolder holder) throws IOException
589     {
590         if (LOG.isDebugEnabled())
591             out.openTag("filter",Collections.singletonMap("source",holder.getSource().toString()));
592         else
593             out.openTag("filter");
594         
595         String n = holder.getName();
596         out.tag("filter-name",n);
597 
598         String ot = n + ".filter.";
599         
600         if (holder instanceof FilterHolder)
601         {
602             out.tag("filter-class",origin(md,ot + "filter-class"),holder.getClassName());
603             out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false");
604         }
605         
606         for (String p : holder.getInitParameters().keySet())
607         {
608             out.openTag("init-param",origin(md,ot + "init-param." + p))
609             .tag("param-name",p)
610             .tag("param-value",holder.getInitParameter(p))
611             .closeTag();
612         }
613 
614         out.closeTag();
615     }
616 
617     private void outholder(XmlAppendable out, MetaData md, ServletHolder holder) throws IOException
618     {
619         
620         if (LOG.isDebugEnabled())
621             out.openTag("servlet",Collections.singletonMap("source",holder.getSource().toString()));
622         else
623             out.openTag("servlet");
624         
625         String n = holder.getName();
626         out.tag("servlet-name",n);
627 
628         String ot = n + ".servlet.";
629 
630         ServletHolder s = (ServletHolder)holder;
631         if (s.getForcedPath() != null && s.getClassName() == null)
632             out.tag("jsp-file",s.getForcedPath());
633         else
634             out.tag("servlet-class",origin(md,ot + "servlet-class"),s.getClassName());
635 
636         for (String p : holder.getInitParameters().keySet())
637         {
638             if ("jsp".equalsIgnoreCase(n) && "scratchdir".equalsIgnoreCase(p)) //don't preconfigure the temp dir for jsp output
639                 continue;
640             out.openTag("init-param",origin(md,ot + "init-param." + p))
641             .tag("param-name",p)
642             .tag("param-value",holder.getInitParameter(p))
643             .closeTag();
644         }
645 
646         if (s.getInitOrder() >= 0)
647             out.tag("load-on-startup",Integer.toString(s.getInitOrder()));
648 
649         if (!s.isEnabled())
650             out.tag("enabled",origin(md,ot + "enabled"),"false");
651 
652         out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false");
653 
654         if (s.getRunAsRole() != null)
655             out.openTag("run-as",origin(md,ot + "run-as"))
656             .tag("role-name",s.getRunAsRole())
657             .closeTag();
658 
659         Map<String,String> roles = s.getRoleRefMap();
660         if (roles!=null)
661         {
662             for (Map.Entry<String, String> e : roles.entrySet())
663             {
664                 out.openTag("security-role-ref",origin(md,ot+"role-name."+e.getKey()))
665                 .tag("role-name",e.getKey())
666                 .tag("role-link",e.getValue())
667                 .closeTag();
668             }
669         }
670 
671         //multipart-config
672         MultipartConfigElement multipartConfig = ((ServletHolder.Registration)s.getRegistration()).getMultipartConfig();
673         if (multipartConfig != null)
674         {
675             out.openTag("multipart-config", origin(md, s.getName()+".servlet.multipart-config"));
676             if (multipartConfig.getLocation() != null)
677                 out.tag("location", multipartConfig.getLocation());
678             out.tag("max-file-size", Long.toString(multipartConfig.getMaxFileSize()));
679             out.tag("max-request-size", Long.toString(multipartConfig.getMaxRequestSize()));
680             out.tag("file-size-threshold", Long.toString(multipartConfig.getFileSizeThreshold()));
681             out.closeTag();
682         }
683 
684         out.closeTag();
685     }
686     
687     
688     /**
689      * Find the origin (web.xml, fragment, annotation etc) of a web artifact from MetaData.
690      * 
691      * @param md the metadata
692      * @param name the name
693      * @return the origin map
694      */
695     public Map<String, String> origin(MetaData md, String name)
696     {
697         if (!LOG.isDebugEnabled())
698             return Collections.emptyMap();
699         if (name == null)
700             return Collections.emptyMap();
701         OriginInfo origin = md.getOriginInfo(name);
702         if (LOG.isDebugEnabled()) LOG.debug("origin of "+name+" is "+origin);
703         if (origin == null)
704             return Collections.emptyMap();
705         return Collections.singletonMap("origin",origin.toString());
706     }
707      
708 }