blob: 34c401222a67b9814214c9142818e2dfff1d4ce2 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
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.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.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 QuickStartDescriptorGenerator
{
private static final Logger LOG = Log.getLogger(QuickStartDescriptorGenerator.class);
public static final String DEFAULT_QUICKSTART_DESCRIPTOR_NAME = "quickstart-web.xml";
protected WebAppContext _webApp;
protected String _extraXML;
/**
* @param w the source WebAppContext
* @param extraXML any extra xml snippet to append
*/
public QuickStartDescriptorGenerator (WebAppContext w, String extraXML)
{
_webApp = w;
_extraXML = extraXML;
}
/**
* 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 (OutputStream stream) throws FileNotFoundException, IOException
{
if (_webApp == null)
throw new IllegalStateException("No webapp for quickstart generation");
if (stream == null)
throw new IllegalStateException("No output for quickstart generation");
_webApp.getMetaData().getOrigins();
if (_webApp.getBaseResource()==null)
throw new IllegalArgumentException("No base resource for "+this);
LOG.info("Quickstart generating");
XmlAppendable out = new XmlAppendable(stream,"UTF-8");
MetaData md = _webApp.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","3.1");
out.openTag("web-app",webappAttr);
if (_webApp.getDisplayName() != null)
out.tag("display-name",_webApp.getDisplayName());
// Set some special context parameters
// The location of the war file on disk
AttributeNormalizer normalizer = new AttributeNormalizer(_webApp.getBaseResource());
// The library order
addContextParamFromAttribute(out,ServletContext.ORDERED_LIBS);
//the servlet container initializers
addContextParamFromAttribute(out,AnnotationConfiguration.CONTAINER_INITIALIZERS);
//the tlds discovered
addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_TLDS,normalizer);
//the META-INF/resources discovered
addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_RESOURCES,normalizer);
// init params
for (String p : _webApp.getInitParams().keySet())
out.openTag("context-param",origin(md,"context-param." + p))
.tag("param-name",p)
.tag("param-value",_webApp.getInitParameter(p))
.closeTag();
if (_webApp.getEventListeners() != null)
for (EventListener e : _webApp.getEventListeners())
out.openTag("listener",origin(md,e.getClass().getCanonicalName() + ".listener"))
.tag("listener-class",e.getClass().getCanonicalName())
.closeTag();
ServletHandler servlets = _webApp.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 =_webApp. 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 (_webApp.getWelcomeFiles() != null)
{
out.openTag("welcome-file-list");
for (String welcomeFile:_webApp.getWelcomeFiles())
{
out.tag("welcome-file", welcomeFile);
}
out.closeTag();
}
Map<String,String> localeEncodings = _webApp.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 (_webApp.getSessionHandler().getSessionManager() != null)
{
out.openTag("session-config");
int maxInactiveSec = _webApp.getSessionHandler().getSessionManager().getMaxInactiveInterval();
out.tag("session-timeout", (maxInactiveSec==0?"0":Integer.toString(maxInactiveSec/60)));
//cookie-config
SessionCookieConfig cookieConfig = _webApp.getSessionHandler().getSessionManager().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 =_webApp. getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes();
if (modes != null)
{
for (SessionTrackingMode mode:modes)
out.tag("tracking-mode", mode.toString());
}
out.closeTag();
}
//error-pages
Map<String,String> errorPages = ((ErrorPageErrorHandler)_webApp.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 = _webApp.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)_webApp.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)_webApp.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();
}
}
out.literal(_extraXML);
out.closeTag();
}
/**
* Turn attribute into context-param to store.
*
* @param out
* @param attribute
* @throws IOException
*/
private void addContextParamFromAttribute(XmlAppendable out, String attribute) throws IOException
{
Object o = _webApp.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
* @param resourceBase
* @throws IOException
*/
private void addContextParamFromAttribute(XmlAppendable out, String attribute, AttributeNormalizer normalizer) throws IOException
{
Object o = _webApp.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 tag
* @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 (!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("origin",origin.toString());
}
}