// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.quickstart; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Collection; import java.util.Collections; import java.util.EventListener; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletContext; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspPropertyGroupDescriptor; import javax.servlet.descriptor.TaglibDescriptor; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.plus.annotation.LifeCycleCallback; import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection; import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.webapp.AbstractConfiguration; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.MetaData; import org.eclipse.jetty.webapp.MetaData.OriginInfo; import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlAppendable; /** * QuickStartDescriptorGenerator * <p> * Generate an effective web.xml from a WebAppContext, including all components * from web.xml, web-fragment.xmls annotations etc. */ public class QuickStartGeneratorConfiguration extends AbstractConfiguration { private static final Logger LOG = Log.getLogger(QuickStartGeneratorConfiguration.class); public static final String ORIGIN = "org.eclipse.jetty.originAttribute"; public static final String DEFAULT_ORIGIN_ATTRIBUTE_NAME = "origin"; protected final boolean _abort; protected String _originAttribute; protected boolean _generateOrigin; protected int _count; protected Resource _quickStartWebXml; public QuickStartGeneratorConfiguration() { this(false); } public QuickStartGeneratorConfiguration(boolean abort) { super(true); _count = 0; _abort = abort; } @Override public boolean abort(WebAppContext context) { return _abort; } public void setOriginAttribute (String name) { _originAttribute = name; } /** * @return the originAttribute */ public String getOriginAttribute() { return _originAttribute; } /** * @return the generateOrigin */ public boolean isGenerateOrigin() { return _generateOrigin; } /** * @param generateOrigin the generateOrigin to set */ public void setGenerateOrigin(boolean generateOrigin) { _generateOrigin = generateOrigin; } public Resource getQuickStartWebXml() { return _quickStartWebXml; } public void setQuickStartWebXml(Resource quickStartWebXml) { _quickStartWebXml = quickStartWebXml; } /** * Perform the generation of the xml file * @param stream the stream to generate the quickstart-web.xml to * @throws IOException if unable to generate the quickstart-web.xml * @throws FileNotFoundException if unable to find the file */ public void generateQuickStartWebXml (WebAppContext context,OutputStream stream) throws FileNotFoundException, IOException { if (context == null) throw new IllegalStateException("No webapp for quickstart generation"); if (stream == null) throw new IllegalStateException("No output for quickstart generation"); if (_originAttribute == null) _originAttribute = DEFAULT_ORIGIN_ATTRIBUTE_NAME; context.getMetaData().getOrigins(); if (context.getBaseResource()==null) throw new IllegalArgumentException("No base resource for "+this); LOG.info("Quickstart generating"); XmlAppendable out = new XmlAppendable(stream,"UTF-8"); MetaData md = context.getMetaData(); Map<String, String> webappAttr = new HashMap<>(); webappAttr.put("xmlns","http://xmlns.jcp.org/xml/ns/javaee"); webappAttr.put("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance"); webappAttr.put("xsi:schemaLocation","http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"); webappAttr.put("metadata-complete","true"); webappAttr.put("version",context.getServletContext().getEffectiveMajorVersion()+"."+context.getServletContext().getEffectiveMinorVersion()); out.openTag("web-app",webappAttr); if (context.getDisplayName() != null) out.tag("display-name",context.getDisplayName()); // Set some special context parameters // The location of the war file on disk AttributeNormalizer normalizer = new AttributeNormalizer(context.getBaseResource()); // The library order addContextParamFromAttribute(context,out,ServletContext.ORDERED_LIBS); //the servlet container initializers addContextParamFromAttribute(context,out,AnnotationConfiguration.CONTAINER_INITIALIZERS); //the tlds discovered addContextParamFromAttribute(context,out,MetaInfConfiguration.METAINF_TLDS,normalizer); //the META-INF/resources discovered addContextParamFromAttribute(context,out,MetaInfConfiguration.METAINF_RESOURCES,normalizer); //add the name of the origin attribute, if it is being used if (_generateOrigin) { out.openTag("context-param") .tag("param-name", ORIGIN) .tag("param-value", _originAttribute) .closeTag(); } // init params for (String p : context.getInitParams().keySet()) out.openTag("context-param",origin(md,"context-param." + p)) .tag("param-name",p) .tag("param-value",context.getInitParameter(p)) .closeTag(); if (context.getEventListeners() != null) for (EventListener e : context.getEventListeners()) out.openTag("listener",origin(md,e.getClass().getCanonicalName() + ".listener")) .tag("listener-class",e.getClass().getCanonicalName()) .closeTag(); ServletHandler servlets = context.getServletHandler(); if (servlets.getFilters() != null) { for (FilterHolder holder : servlets.getFilters()) outholder(out,md,holder); } if (servlets.getFilterMappings() != null) { for (FilterMapping mapping : servlets.getFilterMappings()) { out.openTag("filter-mapping"); out.tag("filter-name",mapping.getFilterName()); if (mapping.getPathSpecs() != null) for (String s : mapping.getPathSpecs()) out.tag("url-pattern",s); if (mapping.getServletNames() != null) for (String n : mapping.getServletNames()) out.tag("servlet-name",n); if (!mapping.isDefaultDispatches()) { if (mapping.appliesTo(DispatcherType.REQUEST)) out.tag("dispatcher","REQUEST"); if (mapping.appliesTo(DispatcherType.ASYNC)) out.tag("dispatcher","ASYNC"); if (mapping.appliesTo(DispatcherType.ERROR)) out.tag("dispatcher","ERROR"); if (mapping.appliesTo(DispatcherType.FORWARD)) out.tag("dispatcher","FORWARD"); if (mapping.appliesTo(DispatcherType.INCLUDE)) out.tag("dispatcher","INCLUDE"); } out.closeTag(); } } if (servlets.getServlets() != null) { for (ServletHolder holder : servlets.getServlets()) outholder(out,md,holder); } if (servlets.getServletMappings() != null) { for (ServletMapping mapping : servlets.getServletMappings()) { out.openTag("servlet-mapping",origin(md,mapping.getServletName() + ".servlet.mappings")); out.tag("servlet-name",mapping.getServletName()); if (mapping.getPathSpecs() != null) for (String s : mapping.getPathSpecs()) out.tag("url-pattern",s); out.closeTag(); } } // Security elements SecurityHandler security =context. getSecurityHandler(); if (security!=null && (security.getRealmName()!=null || security.getAuthMethod()!=null)) { out.openTag("login-config"); if (security.getAuthMethod()!=null) out.tag("auth-method",origin(md,"auth-method"),security.getAuthMethod()); if (security.getRealmName()!=null) out.tag("realm-name",origin(md,"realm-name"),security.getRealmName()); if (Constraint.__FORM_AUTH.equalsIgnoreCase(security.getAuthMethod())) { out.openTag("form-login-config"); out.tag("form-login-page",origin(md,"form-login-page"),security.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE)); out.tag("form-error-page",origin(md,"form-error-page"),security.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE)); out.closeTag(); } out.closeTag(); } if (security instanceof ConstraintAware) { ConstraintAware ca = (ConstraintAware)security; for (String r:ca.getRoles()) out.openTag("security-role") .tag("role-name",r) .closeTag(); for (ConstraintMapping m : ca.getConstraintMappings()) { out.openTag("security-constraint"); out.openTag("web-resource-collection"); { if (m.getConstraint().getName()!=null) out.tag("web-resource-name",m.getConstraint().getName()); if (m.getPathSpec()!=null) out.tag("url-pattern",origin(md,"constraint.url."+m.getPathSpec()),m.getPathSpec()); if (m.getMethod()!=null) out.tag("http-method",m.getMethod()); if (m.getMethodOmissions()!=null) for (String o:m.getMethodOmissions()) out.tag("http-method-omission",o); out.closeTag(); } if (m.getConstraint().getAuthenticate()) { String[] roles = m.getConstraint().getRoles(); if (roles!=null && roles.length>0) { out.openTag("auth-constraint"); if (m.getConstraint().getRoles()!=null) for (String r : m.getConstraint().getRoles()) out.tag("role-name",r); out.closeTag(); } else out.tag("auth-constraint"); } switch (m.getConstraint().getDataConstraint()) { case Constraint.DC_NONE: out.openTag("user-data-constraint").tag("transport-guarantee","NONE").closeTag(); break; case Constraint.DC_INTEGRAL: out.openTag("user-data-constraint").tag("transport-guarantee","INTEGRAL").closeTag(); break; case Constraint.DC_CONFIDENTIAL: out.openTag("user-data-constraint").tag("transport-guarantee","CONFIDENTIAL").closeTag(); break; default: break; } out.closeTag(); } } if (context.getWelcomeFiles() != null) { out.openTag("welcome-file-list"); for (String welcomeFile:context.getWelcomeFiles()) { out.tag("welcome-file", welcomeFile); } out.closeTag(); } Map<String,String> localeEncodings = context.getLocaleEncodings(); if (localeEncodings != null && !localeEncodings.isEmpty()) { out.openTag("locale-encoding-mapping-list"); for (Map.Entry<String, String> entry:localeEncodings.entrySet()) { out.openTag("locale-encoding-mapping", origin(md,"locale-encoding."+entry.getKey())); out.tag("locale", entry.getKey()); out.tag("encoding", entry.getValue()); out.closeTag(); } out.closeTag(); } //session-config if (context.getSessionHandler() != null) { out.openTag("session-config"); int maxInactiveSec = context.getSessionHandler().getMaxInactiveInterval(); out.tag("session-timeout", (maxInactiveSec==0?"0":Integer.toString(maxInactiveSec/60))); //cookie-config SessionCookieConfig cookieConfig = context.getSessionHandler().getSessionCookieConfig(); if (cookieConfig != null) { out.openTag("cookie-config"); if (cookieConfig.getName() != null) out.tag("name", origin(md,"cookie-config.name"), cookieConfig.getName()); if (cookieConfig.getDomain() != null) out.tag("domain", origin(md, "cookie-config.domain"), cookieConfig.getDomain()); if (cookieConfig.getPath() != null) out.tag("path", origin(md, "cookie-config.path"), cookieConfig.getPath()); if (cookieConfig.getComment() != null) out.tag("comment", origin(md, "cookie-config.comment"), cookieConfig.getComment()); out.tag("http-only", origin(md, "cookie-config.http-only"), Boolean.toString(cookieConfig.isHttpOnly())); out.tag("secure", origin(md, "cookie-config.secure"), Boolean.toString(cookieConfig.isSecure())); out.tag("max-age", origin(md, "cookie-config.max-age"), Integer.toString(cookieConfig.getMaxAge())); out.closeTag(); } // tracking-modes Set<SessionTrackingMode> modes =context. getSessionHandler().getEffectiveSessionTrackingModes(); if (modes != null) { for (SessionTrackingMode mode:modes) out.tag("tracking-mode", mode.toString()); } out.closeTag(); } //error-pages Map<String,String> errorPages = ((ErrorPageErrorHandler)context.getErrorHandler()).getErrorPages(); if (errorPages != null) { for (Map.Entry<String, String> entry:errorPages.entrySet()) { out.openTag("error-page", origin(md, "error."+entry.getKey())); //a global or default error page has no code or exception if (!ErrorPageErrorHandler.GLOBAL_ERROR_PAGE.equals(entry.getKey())) { if (entry.getKey().matches("\\d{3}")) out.tag("error-code", entry.getKey()); else out.tag("exception-type", entry.getKey()); } out.tag("location", entry.getValue()); out.closeTag(); } } //mime-types MimeTypes mimeTypes = context.getMimeTypes(); if (mimeTypes != null) { for (Map.Entry<String, String> entry:mimeTypes.getMimeMap().entrySet()) { out.openTag("mime-mapping"); out.tag("extension", origin(md, "extension."+entry.getKey()), entry.getKey()); out.tag("mime-type", entry.getValue()); out.closeTag(); } } //jsp-config JspConfig jspConfig = (JspConfig)context.getServletContext().getJspConfigDescriptor(); if (jspConfig != null) { out.openTag("jsp-config"); Collection<TaglibDescriptor> tlds = jspConfig.getTaglibs(); if (tlds != null && !tlds.isEmpty()) { for (TaglibDescriptor tld:tlds) { out.openTag("taglib"); out.tag("taglib-uri", tld.getTaglibURI()); out.tag("taglib-location", tld.getTaglibLocation()); out.closeTag(); } } Collection<JspPropertyGroupDescriptor> jspPropertyGroups = jspConfig.getJspPropertyGroups(); if (jspPropertyGroups != null && !jspPropertyGroups.isEmpty()) { for (JspPropertyGroupDescriptor jspPropertyGroup:jspPropertyGroups) { out.openTag("jsp-property-group"); Collection<String> strings = jspPropertyGroup.getUrlPatterns(); if (strings != null && !strings.isEmpty()) { for (String urlPattern:strings) out.tag("url-pattern", urlPattern); } if (jspPropertyGroup.getElIgnored() != null) out.tag("el-ignored", jspPropertyGroup.getElIgnored()); if (jspPropertyGroup.getPageEncoding() != null) out.tag("page-encoding", jspPropertyGroup.getPageEncoding()); if (jspPropertyGroup.getScriptingInvalid() != null) out.tag("scripting-invalid", jspPropertyGroup.getScriptingInvalid()); if (jspPropertyGroup.getIsXml() != null) out.tag("is-xml", jspPropertyGroup.getIsXml()); if (jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral() != null) out.tag("deferred-syntax-allowed-as-literal", jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral()); if (jspPropertyGroup.getTrimDirectiveWhitespaces() != null) out.tag("trim-directive-whitespaces", jspPropertyGroup.getTrimDirectiveWhitespaces()); if (jspPropertyGroup.getDefaultContentType() != null) out.tag("default-content-type", jspPropertyGroup.getDefaultContentType()); if (jspPropertyGroup.getBuffer() != null) out.tag("buffer", jspPropertyGroup.getBuffer()); if (jspPropertyGroup.getErrorOnUndeclaredNamespace() != null) out.tag("error-on-undeclared-namespace", jspPropertyGroup.getErrorOnUndeclaredNamespace()); strings = jspPropertyGroup.getIncludePreludes(); if (strings != null && !strings.isEmpty()) { for (String prelude:strings) out.tag("include-prelude", prelude); } strings = jspPropertyGroup.getIncludeCodas(); if (strings != null && !strings.isEmpty()) { for (String coda:strings) out.tag("include-coda", coda); } out.closeTag(); } } out.closeTag(); } //lifecycle: post-construct, pre-destroy LifeCycleCallbackCollection lifecycles = ((LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION)); if (lifecycles != null) { Collection<LifeCycleCallback> tmp = lifecycles.getPostConstructCallbacks(); for (LifeCycleCallback c:tmp) { out.openTag("post-construct"); out.tag("lifecycle-callback-class", c.getTargetClassName()); out.tag("lifecycle-callback-method", c.getMethodName()); out.closeTag(); } tmp = lifecycles.getPreDestroyCallbacks(); for (LifeCycleCallback c:tmp) { out.openTag("pre-destroy"); out.tag("lifecycle-callback-class", c.getTargetClassName()); out.tag("lifecycle-callback-method", c.getMethodName()); out.closeTag(); } } ExtraXmlDescriptorProcessor extraXmlProcessor = (ExtraXmlDescriptorProcessor)context.getAttribute(ExtraXmlDescriptorProcessor.class.getName()); out.literal(extraXmlProcessor.getXML()); out.closeTag(); } /** * Turn attribute into context-param to store. * * @param out * @param attribute * @throws IOException */ private void addContextParamFromAttribute(WebAppContext context, XmlAppendable out, String attribute) throws IOException { Object o = context.getAttribute(attribute); if (o == null) return; Collection<?> c = (o instanceof Collection)? (Collection<?>)o:Collections.singletonList(o); StringBuilder v=new StringBuilder(); for (Object i:c) { if (i!=null) { if (v.length()>0) v.append(",\n "); else v.append("\n "); QuotedStringTokenizer.quote(v,i.toString()); } } out.openTag("context-param") .tag("param-name",attribute) .tagCDATA("param-value",v.toString()) .closeTag(); } /** * Turn context attribute into context-param to store. * * @param out * @param attribute * @throws IOException */ private void addContextParamFromAttribute(WebAppContext context, XmlAppendable out, String attribute, AttributeNormalizer normalizer) throws IOException { Object o = context.getAttribute(attribute); if (o == null) return; Collection<?> c = (o instanceof Collection)? (Collection<?>)o:Collections.singletonList(o); StringBuilder v=new StringBuilder(); for (Object i:c) { if (i!=null) { if (v.length()>0) v.append(",\n "); else v.append("\n "); QuotedStringTokenizer.quote(v,normalizer.normalize(i)); } } out.openTag("context-param") .tag("param-name",attribute) .tagCDATA("param-value",v.toString()) .closeTag(); } /** * Generate xml for a Holder (Filter/Servlet) * * @param out * @param md * @param holder * @throws IOException */ private void outholder(XmlAppendable out, MetaData md, FilterHolder holder) throws IOException { if (LOG.isDebugEnabled()) out.openTag("filter",Collections.singletonMap("source",holder.getSource().toString())); else out.openTag("filter"); String n = holder.getName(); out.tag("filter-name",n); String ot = n + ".filter."; if (holder instanceof FilterHolder) { out.tag("filter-class",origin(md,ot + "filter-class"),holder.getClassName()); out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false"); } for (String p : holder.getInitParameters().keySet()) { out.openTag("init-param",origin(md,ot + "init-param." + p)) .tag("param-name",p) .tag("param-value",holder.getInitParameter(p)) .closeTag(); } out.closeTag(); } private void outholder(XmlAppendable out, MetaData md, ServletHolder holder) throws IOException { if (LOG.isDebugEnabled()) out.openTag("servlet",Collections.singletonMap("source",holder.getSource().toString())); else out.openTag("servlet"); String n = holder.getName(); out.tag("servlet-name",n); String ot = n + ".servlet."; ServletHolder s = (ServletHolder)holder; if (s.getForcedPath() != null && s.getClassName() == null) out.tag("jsp-file",s.getForcedPath()); else out.tag("servlet-class",origin(md,ot + "servlet-class"),s.getClassName()); for (String p : holder.getInitParameters().keySet()) { if ("jsp".equalsIgnoreCase(n) && "scratchdir".equalsIgnoreCase(p)) //don't preconfigure the temp dir for jsp output continue; out.openTag("init-param",origin(md,ot + "init-param." + p)) .tag("param-name",p) .tag("param-value",holder.getInitParameter(p)) .closeTag(); } if (s.getInitOrder() >= 0) out.tag("load-on-startup",Integer.toString(s.getInitOrder())); if (!s.isEnabled()) out.tag("enabled",origin(md,ot + "enabled"),"false"); out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false"); if (s.getRunAsRole() != null) out.openTag("run-as",origin(md,ot + "run-as")) .tag("role-name",s.getRunAsRole()) .closeTag(); Map<String,String> roles = s.getRoleRefMap(); if (roles!=null) { for (Map.Entry<String, String> e : roles.entrySet()) { out.openTag("security-role-ref",origin(md,ot+"role-name."+e.getKey())) .tag("role-name",e.getKey()) .tag("role-link",e.getValue()) .closeTag(); } } //multipart-config MultipartConfigElement multipartConfig = ((ServletHolder.Registration)s.getRegistration()).getMultipartConfig(); if (multipartConfig != null) { out.openTag("multipart-config", origin(md, s.getName()+".servlet.multipart-config")); if (multipartConfig.getLocation() != null) out.tag("location", multipartConfig.getLocation()); out.tag("max-file-size", Long.toString(multipartConfig.getMaxFileSize())); out.tag("max-request-size", Long.toString(multipartConfig.getMaxRequestSize())); out.tag("file-size-threshold", Long.toString(multipartConfig.getFileSizeThreshold())); out.closeTag(); } out.closeTag(); } /** * Find the origin (web.xml, fragment, annotation etc) of a web artifact from MetaData. * * @param md the metadata * @param name the name * @return the origin map */ public Map<String, String> origin(MetaData md, String name) { if (!(_generateOrigin || LOG.isDebugEnabled())) return Collections.emptyMap(); if (name == null) return Collections.emptyMap(); OriginInfo origin = md.getOriginInfo(name); if (LOG.isDebugEnabled()) LOG.debug("origin of "+name+" is "+origin); if (origin == null) return Collections.emptyMap(); return Collections.singletonMap(_originAttribute,origin.toString()+":"+(_count++)); } @Override public void preConfigure(WebAppContext context) throws Exception { ExtraXmlDescriptorProcessor extraXmlProcessor = new ExtraXmlDescriptorProcessor(); context.getMetaData().addDescriptorProcessor(extraXmlProcessor); context.setAttribute(ExtraXmlDescriptorProcessor.class.getName(),extraXmlProcessor); super.preConfigure(context); } @Override public void configure(WebAppContext context) throws Exception { MetaData metadata = context.getMetaData(); metadata.resolve(context); Resource quickStartWebXml = _quickStartWebXml; if (_quickStartWebXml == null) quickStartWebXml = context.getBaseResource().addPath("/WEB-INF/quickstart-web.xml"); if (!quickStartWebXml.exists()) quickStartWebXml.getFile().createNewFile(); try (FileOutputStream fos = new FileOutputStream(quickStartWebXml.getFile(),false)) { generateQuickStartWebXml(context,fos); } } }