From 119718d86fb773ae9c5839639107de4be126999e Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 17 Mar 2014 18:22:36 +1100 Subject: [PATCH] added jetty-quickstart module --- .../src/main/config/etc/jetty-deploy.xml | 2 +- .../deploy/providers/WebAppProvider.java | 2 +- jetty-distribution/pom.xml | 5 + jetty-quickstart/README.txt | 6 + jetty-quickstart/pom.xml | 170 +++++ .../main/config/etc/example-quickstart.xml | 21 + .../src/main/config/modules/quickstart.mod | 12 + .../PreconfigureDescriptorProcessor.java | 88 +++ .../quickstart/PreconfigureQuickStartWar.java | 129 ++++ .../quickstart/QuickStartConfiguration.java | 139 ++++ .../QuickStartDescriptorProcessor.java | 218 ++++++ .../jetty/quickstart/QuickStartWebApp.java | 720 ++++++++++++++++++ .../jetty/quickstart/PreconfigureJNDIWar.java | 49 ++ .../jetty/quickstart/PreconfigureSpecWar.java | 58 ++ .../PreconfigureStandardTestWar.java | 62 ++ .../jetty/quickstart/QuickStartJNDIWar.java | 31 + .../jetty/quickstart/QuickStartSpecWar.java | 30 + .../quickstart/QuickStartStandardTestWar.java | 30 + .../eclipse/jetty/quickstart/Quickstart.java | 73 ++ .../src/test/resources/realm.properties | 21 + .../src/test/resources/test-jndi.xml | 60 ++ .../src/test/resources/test-spec.xml | 39 + jetty-quickstart/src/test/resources/test.xml | 45 ++ .../java/org/eclipse/jetty/util/log/Log.java | 29 +- .../org/eclipse/jetty/webapp/MetaData.java | 5 + .../eclipse/jetty/webapp/WebAppContext.java | 17 +- pom.xml | 1 + 27 files changed, 2036 insertions(+), 26 deletions(-) create mode 100644 jetty-quickstart/README.txt create mode 100644 jetty-quickstart/pom.xml create mode 100644 jetty-quickstart/src/main/config/etc/example-quickstart.xml create mode 100644 jetty-quickstart/src/main/config/modules/quickstart.mod create mode 100644 jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java create mode 100644 jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java create mode 100644 jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java create mode 100644 jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java create mode 100644 jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java create mode 100644 jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java create mode 100644 jetty-quickstart/src/test/resources/realm.properties create mode 100644 jetty-quickstart/src/test/resources/test-jndi.xml create mode 100644 jetty-quickstart/src/test/resources/test-spec.xml create mode 100644 jetty-quickstart/src/test/resources/test.xml diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml index 16e5253a86c..c368a06d8fb 100644 --- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml +++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml @@ -18,7 +18,7 @@ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern - .*/servlet-api-[^/]*\.jar$ + .*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/org.apache.taglibs.taglibs-standard-impl-.*\\.jar$ diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java index add8f76ce7e..7e50a964333 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java @@ -280,7 +280,7 @@ public class WebAppProvider extends ScanningAppProvider } } }; - + xmlc.getIdMap().put("Server", getDeploymentManager().getServer()); xmlc.getProperties().put("jetty.home",System.getProperty("jetty.home",".")); xmlc.getProperties().put("jetty.base",System.getProperty("jetty.base",".")); diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 347c48062c1..433cc613db5 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -695,6 +695,11 @@ jetty-monitor ${project.version} + + org.eclipse.jetty + jetty-quickstart + ${project.version} + org.eclipse.jetty jetty-start diff --git a/jetty-quickstart/README.txt b/jetty-quickstart/README.txt new file mode 100644 index 00000000000..43ad7f3e9f8 --- /dev/null +++ b/jetty-quickstart/README.txt @@ -0,0 +1,6 @@ +mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.StartBenchmarkWar" + +OR + +mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.PreconfigureBenchmarkWar" +mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.QuickStartBenchmarkWar" diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml new file mode 100644 index 00000000000..5f9f03cf978 --- /dev/null +++ b/jetty-quickstart/pom.xml @@ -0,0 +1,170 @@ + + + org.eclipse.jetty + jetty-project + 9.1.4-SNAPSHOT + + 4.0.0 + org.eclipse.jetty + jetty-quickstart + Example :: Jetty Quick Start + Jetty Quick Start + http://www.eclipse.org/jetty + + + org.eclipse.jetty + jetty-webapp + ${project.version} + + + org.eclipse.jetty + jetty-jmx + ${project.version} + + + org.eclipse.jetty + jetty-plus + ${project.version} + + + org.eclipse.jetty + jetty-annotations + ${project.version} + + + javax.transaction + javax.transaction-api + 1.2 + compile + + + org.eclipse.jetty.tests + test-mock-resources + ${project.version} + + + org.eclipse.jetty.orbit + javax.mail.glassfish + 1.4.1.v201005082020 + + + org.eclipse.jetty + jetty-servlets + ${project.version} + test + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${project.version} + test + + + org.eclipse.jetty.websocket + websocket-server + ${project.version} + test + + + org.eclipse.jetty + apache-jsp + ${project.version} + test + + + org.eclipse.jetty + apache-jstl + ${project.version} + test + + + + + + + org.codehaus.mojo + appassembler-maven-plugin + 1.7 + + + unix + + + + preconfigure + org.eclipse.jetty.quickstart.PreconfigureQuickStartWar + + + org.eclipse.jetty.quickstart.QuickStartWar + quickstart + + + + + + + maven-dependency-plugin + + + copy + generate-resources + + copy + + + + + org.eclipse.jetty.tests + test-jndi-webapp + ${project.version} + war + true + ** + ${basedir}/target + test-jndi.war + + + org.eclipse.jetty.tests + test-spec-webapp + ${project.version} + war + true + ** + ${basedir}/target + test-spec.war + + + org.eclipse.jetty + test-jetty-webapp + ${project.version} + war + true + ** + ${basedir}/target + test-standard.war + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + config + + + + + + + + diff --git a/jetty-quickstart/src/main/config/etc/example-quickstart.xml b/jetty-quickstart/src/main/config/etc/example-quickstart.xml new file mode 100644 index 00000000000..9a012c701ad --- /dev/null +++ b/jetty-quickstart/src/main/config/etc/example-quickstart.xml @@ -0,0 +1,21 @@ + + + + + + true + / + /application.war + diff --git a/jetty-quickstart/src/main/config/modules/quickstart.mod b/jetty-quickstart/src/main/config/modules/quickstart.mod new file mode 100644 index 00000000000..89db9fd4fec --- /dev/null +++ b/jetty-quickstart/src/main/config/modules/quickstart.mod @@ -0,0 +1,12 @@ +# +# Jetty Quickstart module +# + +[depend] +server +plus +annotations + + +[lib] +lib/jetty-quickstart-${jetty.version}.jar diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java new file mode 100644 index 00000000000..627277e2ebf --- /dev/null +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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 org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.Descriptor; +import org.eclipse.jetty.webapp.IterativeDescriptorProcessor; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlParser; + +/** + * Preconfigure DescriptorProcessor + * + * Saves literal XML snippets + * + */ + +public class PreconfigureDescriptorProcessor extends IterativeDescriptorProcessor +{ + private static final Logger LOG = Log.getLogger(PreconfigureDescriptorProcessor.class); + + private final StringBuilder _buffer = new StringBuilder(); + private final boolean _showOrigin; + private String _origin; + + public PreconfigureDescriptorProcessor () + { + _showOrigin=LOG.isDebugEnabled(); + try + { + registerVisitor("env-entry", getClass().getDeclaredMethod("saveSnippet", __signature)); + registerVisitor("resource-ref", getClass().getDeclaredMethod("saveSnippet", __signature)); + registerVisitor("resource-env-ref", getClass().getDeclaredMethod("saveSnippet", __signature)); + registerVisitor("message-destination-ref", getClass().getDeclaredMethod("saveSnippet", __signature)); + registerVisitor("data-source", getClass().getDeclaredMethod("saveSnippet", __signature)); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + @Override + public void start(WebAppContext context, Descriptor descriptor) + { + LOG.debug("process {}",descriptor); + _origin=(" \n"); + } + + + @Override + public void end(WebAppContext context,Descriptor descriptor) + { + } + + + public void saveSnippet (WebAppContext context, Descriptor descriptor, XmlParser.Node node) + throws Exception + { + LOG.debug("save {}",node.getTag()); + if (_showOrigin) + _buffer.append(_origin); + _buffer.append(" ").append(node.toString()).append("\n"); + } + + public String getXML() + { + return _buffer.toString(); + } + +} diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java new file mode 100644 index 00000000000..204ad2ecd15 --- /dev/null +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java @@ -0,0 +1,129 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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 org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.JarResource; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.xml.XmlConfiguration; + +public class PreconfigureQuickStartWar +{ + private static final Logger LOG = Log.getLogger(PreconfigureQuickStartWar.class); + static final boolean ORIGIN=LOG.isDebugEnabled(); + + + public static void main(String... args) throws Exception + { + Resource war = null; + Resource dir = null; + Resource xml = null; + + switch (args.length) + { + case 0: + error("No WAR file or directory given"); + break; + + case 1: + dir = Resource.newResource(args[0]); + + case 2: + war = Resource.newResource(args[0]); + if (war.isDirectory()) + { + dir = war; + war = null; + xml = Resource.newResource(args[1]); + } + else + { + dir = Resource.newResource(args[1]); + } + + break; + + case 3: + war = Resource.newResource(args[0]); + dir = Resource.newResource(args[1]); + xml = Resource.newResource(args[2]); + break; + + default: + error("Too many args"); + break; + } + + + preconfigure(war,dir,xml); + } + + /** + * @param war The war (or directory) to preconfigure + * @param dir The directory to expand the war into (or null if war is a directory) + * @param xml A context XML to apply (or null if none) + * @throws Exception + */ + public static void preconfigure(Resource war, Resource dir, Resource xml) throws Exception + { + // Do we need to unpack a war? + if (war != null) + { + if (war.isDirectory()) + error("war file is directory"); + + if (!dir.exists()) + dir.getFile().mkdirs(); + JarResource.newJarResource(war).copyTo(dir.getFile()); + } + + final Server server = new Server(); + + QuickStartWebApp webapp = new QuickStartWebApp(); + + if (xml != null) + { + if (xml.isDirectory() || !xml.toString().toLowerCase().endsWith(".xml")) + error("Bad context.xml: "+xml); + XmlConfiguration xmlConfiguration = new XmlConfiguration(xml.getURL()); + xmlConfiguration.configure(webapp); + } + webapp.setResourceBase(dir.getFile().getAbsolutePath()); + webapp.setPreconfigure(true); + server.setHandler(webapp); + server.start(); + server.stop(); + } + + + + + private static void error(String message) + { + System.err.println("ERROR: " + message); + System.err.println("Usage: java -jar PreconfigureQuickStartWar.jar "); + System.err.println(" java -jar PreconfigureQuickStartWar.jar "); + System.err.println(" java -jar PreconfigureQuickStartWar.jar "); + System.err.println(" java -jar PreconfigureQuickStartWar.jar "); + System.exit(1); + } + +} diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java new file mode 100644 index 00000000000..313c5c03c86 --- /dev/null +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java @@ -0,0 +1,139 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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 org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.annotations.AnnotationDecorator; +import org.eclipse.jetty.annotations.ServletContainerInitializersStarter; +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.webapp.StandardDescriptorProcessor; +import org.eclipse.jetty.webapp.WebAppClassLoader; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; + +/** + * QuickStartConfiguration + * + * Re-inflate a deployable webapp from a saved effective-web.xml + * which combines all pre-parsed web xml descriptors and annotations. + * + */ +public class QuickStartConfiguration extends WebInfConfiguration +{ + private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class); + + /** + * @see org.eclipse.jetty.webapp.AbstractConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void preConfigure(WebAppContext context) throws Exception + { + //check that webapp is suitable for quick start - it is not a packed war + String war = context.getWar(); + if (war == null || war.length()<=0) + throw new IllegalStateException ("No location for webapp"); + + //Make a temp directory for the webapp if one is not already set + resolveTempDirectory(context); + + Resource webApp = context.newResource(war); + + // Accept aliases for WAR files + if (webApp.getAlias() != null) + { + LOG.debug(webApp + " anti-aliased to " + webApp.getAlias()); + webApp = context.newResource(webApp.getAlias()); + } + + // Is the WAR usable directly? + if (!webApp.exists() || !webApp.isDirectory() || webApp.toString().startsWith("jar:")) + throw new IllegalStateException("Webapp does not exist or is not unpacked"); + + context.setBaseResource(webApp); + + LOG.debug("webapp={}",webApp); + + + //look for effective-web.xml in WEB-INF of webapp + Resource webInf = context.getWebInf(); + if (webInf == null || !webInf.exists()) + throw new IllegalStateException("No WEB-INF"); + LOG.debug("webinf={}",webInf); + + Resource quickStartWebXml = webInf.addPath("quickstart-web.xml"); + if (!quickStartWebXml.exists()) + throw new IllegalStateException ("No WEB-INF/quickstart-web.xml"); + LOG.debug("quickStartWebXml={}",quickStartWebXml); + + context.getMetaData().setWebXml(quickStartWebXml); + } + + + /** + * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void configure(WebAppContext context) throws Exception + { + LOG.debug("configure {}",this); + if (context.isStarted()) + { + LOG.warn("Cannot configure webapp after it is started"); + return; + } + + //Temporary: set up the classpath here. This should be handled by the QuickStartDescriptorProcessor + Resource webInf = context.getWebInf(); + + if (webInf != null && webInf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader) + { + // Look for classes directory + Resource classes= webInf.addPath("classes/"); + if (classes.exists()) + ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes); + + // Look for jars + Resource lib= webInf.addPath("lib/"); + if (lib.exists() || lib.isDirectory()) + ((WebAppClassLoader)context.getClassLoader()).addJars(lib); + } + + //add the processor to handle normal web.xml content + context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor()); + + //add a processor to handle extended web.xml format + context.getMetaData().addDescriptorProcessor(new QuickStartDescriptorProcessor()); + + //add a decorator that will find introspectable annotations + context.addDecorator(new AnnotationDecorator(context)); //this must be the last Decorator because they are run in reverse order! + + //add a context bean that will run ServletContainerInitializers as the context starts + ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER); + if (starter != null) + throw new IllegalStateException("ServletContainerInitializersStarter already exists"); + starter = new ServletContainerInitializersStarter(context); + context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER, starter); + context.addBean(starter, true); + + LOG.debug("configured {}",this); + } + +} diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java new file mode 100644 index 00000000000..7e5a6c849b9 --- /dev/null +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java @@ -0,0 +1,218 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import javax.servlet.ServletContext; + +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.annotations.ServletContainerInitializersStarter; +import org.eclipse.jetty.plus.annotation.ContainerInitializer; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; +import org.eclipse.jetty.webapp.Descriptor; +import org.eclipse.jetty.webapp.IterativeDescriptorProcessor; +import org.eclipse.jetty.webapp.MetaInfConfiguration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlParser; + +/** + * QuickStartDescriptorProcessor + * + * Handle extended elements for quickstart-web.xml + */ +public class QuickStartDescriptorProcessor extends IterativeDescriptorProcessor +{ + /** + * + */ + public QuickStartDescriptorProcessor() + { + try + { + registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature)); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + /** + * @see org.eclipse.jetty.webapp.IterativeDescriptorProcessor#start(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.Descriptor) + */ + @Override + public void start(WebAppContext context, Descriptor descriptor) + { + } + + /** + * @see org.eclipse.jetty.webapp.IterativeDescriptorProcessor#end(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.Descriptor) + */ + @Override + public void end(WebAppContext context, Descriptor descriptor) + { + } + + + /** + * @param context + * @param descriptor + * @param node + */ + public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node) + throws Exception + { + String name = node.getString("param-name", false, true); + String value = node.getString("param-value", false, true); + List values = new ArrayList<>(); + + // extract values + switch(name) + { + case ServletContext.ORDERED_LIBS: + case AnnotationConfiguration.CONTAINER_INITIALIZERS: + case MetaInfConfiguration.METAINF_TLDS: + case MetaInfConfiguration.METAINF_RESOURCES: + + context.removeAttribute(name); + + QuotedStringTokenizer tok = new QuotedStringTokenizer(value,","); + while(tok.hasMoreElements()) + values.add(tok.nextToken().trim()); + + break; + + default: + values.add(value); + } + + // handle values + switch(name) + { + case ServletContext.ORDERED_LIBS: + { + List libs = new ArrayList<>(); + Object o=context.getAttribute(ServletContext.ORDERED_LIBS); + if (o instanceof Collection) + libs.addAll((Collection)o); + libs.addAll(values); + if (libs.size()>0) + context.setAttribute(ServletContext.ORDERED_LIBS,libs); + + break; + } + + case AnnotationConfiguration.CONTAINER_INITIALIZERS: + { + for (String i : values) + visitContainerInitializer(context, new ContainerInitializer(Thread.currentThread().getContextClassLoader(), i)); + break; + } + + case MetaInfConfiguration.METAINF_TLDS: + { + List tlds = new ArrayList<>(); + String war=context.getBaseResource().getURI().toString(); + Object o=context.getAttribute(MetaInfConfiguration.METAINF_TLDS); + if (o instanceof Collection) + tlds.addAll((Collection)o); + for (String i : values) + { + Resource r = Resource.newResource(i.replace("${WAR}/",war)); + if (r.exists()) + tlds.add(r.getURL()); + else + throw new IllegalArgumentException("TLD not found: "+r); + } + + if (tlds.size()>0) + context.setAttribute(MetaInfConfiguration.METAINF_TLDS,tlds); + break; + } + + case MetaInfConfiguration.METAINF_RESOURCES: + { + String war=context.getBaseResource().getURI().toString(); + for (String i : values) + { + Resource r = Resource.newResource(i.replace("${WAR}/",war)); + if (r.exists()) + visitMetaInfResource(context,r); + else + throw new IllegalArgumentException("Resource not found: "+r); + } + break; + } + + default: + + } + } + + + public void visitContainerInitializer (WebAppContext context, ContainerInitializer containerInitializer) + { + if (containerInitializer == null) + return; + + //add the ContainerInitializer to the list of container initializers + List containerInitializers = (List)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS); + if (containerInitializers == null) + { + containerInitializers = new ArrayList(); + context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS, containerInitializers); + } + + containerInitializers.add(containerInitializer); + + //Ensure a bean is set up on the context that will invoke the ContainerInitializers as the context starts + ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER); + if (starter == null) + { + starter = new ServletContainerInitializersStarter(context); + context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER, starter); + context.addBean(starter, true); + } + } + + + public void visitMetaInfResource (WebAppContext context, Resource dir) + { + Collection metaInfResources = (Collection)context.getAttribute(MetaInfConfiguration.METAINF_RESOURCES); + if (metaInfResources == null) + { + metaInfResources = new HashSet(); + context.setAttribute(MetaInfConfiguration.METAINF_RESOURCES, metaInfResources); + } + metaInfResources.add(dir); + //also add to base resource of webapp + Resource[] collection=new Resource[metaInfResources.size()+1]; + int i=0; + collection[i++]=context.getBaseResource(); + for (Resource resource : metaInfResources) + collection[i++]=resource; + context.setBaseResource(new ResourceCollection(collection)); + } +} diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java new file mode 100644 index 00000000000..bcbcab80808 --- /dev/null +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java @@ -0,0 +1,720 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +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.Holder; +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.JarResource; +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; + +/** + * QuickStartWar + * + */ +public class QuickStartWebApp extends WebAppContext +{ + private static final Logger LOG = Log.getLogger(QuickStartWebApp.class); + + public static final String[] __configurationClasses = new String[] + { + org.eclipse.jetty.quickstart.QuickStartConfiguration.class.getCanonicalName(), + org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(), + org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(), + org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName() + }; + + + private boolean _preconfigure=false; + private boolean _autoPreconfigure=false; + private boolean _startWebapp=false; + private PreconfigureDescriptorProcessor _preconfigProcessor; + + + public static final String[] __preconfigurationClasses = new String[] + { + org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(), + org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(), + org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(), + org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(), + org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(), + org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(), + org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(), + }; + + public QuickStartWebApp() + { + super(); + setConfigurationClasses(__preconfigurationClasses); + setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",".*\\.jar"); + } + + public boolean isPreconfigure() + { + return _preconfigure; + } + + /* ------------------------------------------------------------ */ + /** Preconfigure webapp + * @param preconfigure If true, then starting the webapp will generate + * the WEB-INF/quickstart-web.xml rather than start the webapp. + */ + public void setPreconfigure(boolean preconfigure) + { + _preconfigure = preconfigure; + } + + public boolean isAutoPreconfigure() + { + return _autoPreconfigure; + } + + public void setAutoPreconfigure(boolean autoPrecompile) + { + _autoPreconfigure = autoPrecompile; + } + + @Override + protected void startWebapp() throws Exception + { + if (isPreconfigure()) + generateQuickstartWebXml(_preconfigProcessor.getXML()); + + if (_startWebapp) + super.startWebapp(); + } + + @Override + protected void doStart() throws Exception + { + // unpack and Adjust paths. + Resource war = null; + Resource dir = null; + + Resource base = getBaseResource(); + if (base==null) + base=Resource.newResource(getWar()); + + if (base.isDirectory()) + dir=base; + else if (base.toString().toLowerCase().endsWith(".war")) + { + war=base; + String w=war.toString(); + dir=Resource.newResource(w.substring(0,w.length()-4)); + + if (!dir.exists()) + { + LOG.info("Quickstart Extract " + war + " to " + dir); + dir.getFile().mkdirs(); + JarResource.newJarResource(war).copyTo(dir.getFile()); + } + + setWar(null); + setBaseResource(dir); + } + else + throw new IllegalArgumentException(); + + + Resource qswebxml=dir.addPath("/WEB-INF/quickstart-web.xml"); + + if (isPreconfigure()) + { + _preconfigProcessor = new PreconfigureDescriptorProcessor(); + getMetaData().addDescriptorProcessor(_preconfigProcessor); + _startWebapp=false; + } + else if (qswebxml.exists()) + { + setConfigurationClasses(__configurationClasses); + _startWebapp=true; + } + else if (_autoPreconfigure) + { + LOG.info("Quickstart preconfigure: {}(war={},dir={})",this,war,dir); + + _preconfigProcessor = new PreconfigureDescriptorProcessor(); + + getMetaData().addDescriptorProcessor(_preconfigProcessor); + setPreconfigure(true); + _startWebapp=true; + } + else + _startWebapp=true; + + super.doStart(); + } + + + public void generateQuickstartWebXml(String extraXML) throws IOException + { + getMetaData().getOrigins(); + // dumpStdErr(); + + if (getBaseResource()==null) + throw new IllegalArgumentException("No base resource for "+this); + + File webxml = new File(getWebInf().getFile(),"quickstart-web.xml"); + + LOG.info("Quickstart generate {}",webxml); + + XmlAppendable out = new XmlAppendable(new FileOutputStream(webxml),"UTF-8"); + MetaData md = getMetaData(); + + Map 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.open("web-app",webappAttr); + + if (getDisplayName() != null) + out.tag("display-name",getDisplayName()); + + // Set some special context parameters + + // The location of the war file on disk + String resourceBase=getBaseResource().getFile().getCanonicalFile().getAbsoluteFile().toURI().toString(); + + // 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,resourceBase); + //the META-INF/resources discovered + addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_RESOURCES,resourceBase); + + + // init params + for (String p : getInitParams().keySet()) + out.open("context-param",origin(md,"context-param." + p)) + .tag("param-name",p) + .tag("param-value",getInitParameter(p)) + .close(); + + if (getEventListeners() != null) + for (EventListener e : getEventListeners()) + out.open("listener",origin(md,e.getClass().getCanonicalName() + ".listener")) + .tag("listener-class",e.getClass().getCanonicalName()) + .close(); + + ServletHandler servlets = getServletHandler(); + + if (servlets.getFilters() != null) + { + for (FilterHolder holder : servlets.getFilters()) + outholder(out,md,"filter",holder); + } + + if (servlets.getFilterMappings() != null) + { + for (FilterMapping mapping : servlets.getFilterMappings()) + { + out.open("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.close(); + } + } + + if (servlets.getServlets() != null) + { + for (ServletHolder holder : servlets.getServlets()) + outholder(out,md,"servlet",holder); + } + + if (servlets.getServletMappings() != null) + { + for (ServletMapping mapping : servlets.getServletMappings()) + { + out.open("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.close(); + } + } + + // Security elements + SecurityHandler security = getSecurityHandler(); + + if (security!=null && (security.getRealmName()!=null || security.getAuthMethod()!=null)) + { + out.open("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.open("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.close(); + } + + out.close(); + } + + if (security instanceof ConstraintAware) + { + ConstraintAware ca = (ConstraintAware)security; + for (String r:ca.getRoles()) + out.open("security-role") + .tag("role-name",r) + .close(); + + for (ConstraintMapping m : ca.getConstraintMappings()) + { + out.open("security-constraint"); + + if (m.getConstraint().getAuthenticate()) + { + out.open("auth-constraint"); + if (m.getConstraint().getRoles()!=null) + for (String r : m.getConstraint().getRoles()) + out.tag("role-name",r); + + out.close(); + } + + switch (m.getConstraint().getDataConstraint()) + { + case Constraint.DC_NONE: + out.open("user-data-constraint").tag("transport-guarantee","NONE").close(); + break; + + case Constraint.DC_INTEGRAL: + out.open("user-data-constraint").tag("transport-guarantee","INTEGRAL").close(); + break; + + case Constraint.DC_CONFIDENTIAL: + out.open("user-data-constraint").tag("transport-guarantee","CONFIDENTIAL").close(); + break; + + default: + break; + + } + + out.open("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.close(); + } + + out.close(); + + } + } + + if (getWelcomeFiles() != null) + { + out.open("welcome-file-list"); + for (String welcomeFile:getWelcomeFiles()) + { + out.tag("welcome-file", welcomeFile); + } + out.close(); + } + + Map localeEncodings = getLocaleEncodings(); + if (localeEncodings != null && !localeEncodings.isEmpty()) + { + out.open("locale-encoding-mapping-list"); + for (Map.Entry entry:localeEncodings.entrySet()) + { + out.open("locale-encoding-mapping", origin(md,"locale-encoding."+entry.getKey())); + out.tag("locale", entry.getKey()); + out.tag("encoding", entry.getValue()); + out.close(); + } + out.close(); + } + + //session-config + if (getSessionHandler().getSessionManager() != null) + { + out.open("session-config"); + int maxInactiveSec = getSessionHandler().getSessionManager().getMaxInactiveInterval(); + out.tag("session-timeout", (maxInactiveSec==0?"0":Integer.toString(maxInactiveSec/60))); + + Set modes = getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes(); + if (modes != null) + { + for (SessionTrackingMode mode:modes) + out.tag("tracking-mode", mode.toString()); + } + + //cookie-config + SessionCookieConfig cookieConfig = getSessionHandler().getSessionManager().getSessionCookieConfig(); + if (cookieConfig != null) + { + out.open("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.close(); + } + out.close(); + } + + //error-pages + Map errorPages = ((ErrorPageErrorHandler)getErrorHandler()).getErrorPages(); + if (errorPages != null) + { + for (Map.Entry entry:errorPages.entrySet()) + { + out.open("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.close(); + } + } + + //mime-types + MimeTypes mimeTypes = getMimeTypes(); + if (mimeTypes != null) + { + for (Map.Entry entry:mimeTypes.getMimeMap().entrySet()) + { + out.open("mime-mapping"); + out.tag("extension", origin(md, "extension."+entry.getKey()), entry.getKey()); + out.tag("mime-type", entry.getValue()); + out.close(); + } + } + + //jsp-config + JspConfig jspConfig = (JspConfig)getServletContext().getJspConfigDescriptor(); + if (jspConfig != null) + { + out.open("jsp-config"); + Collection tlds = jspConfig.getTaglibs(); + if (tlds != null && !tlds.isEmpty()) + { + for (TaglibDescriptor tld:tlds) + { + out.open("taglib"); + out.tag("taglib-uri", tld.getTaglibURI()); + out.tag("taglib-location", tld.getTaglibLocation()); + out.close(); + } + } + + Collection jspPropertyGroups = jspConfig.getJspPropertyGroups(); + if (jspPropertyGroups != null && !jspPropertyGroups.isEmpty()) + { + for (JspPropertyGroupDescriptor jspPropertyGroup:jspPropertyGroups) + { + out.open("jsp-property-group"); + Collection 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.close(); + } + } + + out.close(); + } + + //lifecycle: post-construct, pre-destroy + LifeCycleCallbackCollection lifecycles = ((LifeCycleCallbackCollection)getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION)); + if (lifecycles != null) + { + Collection tmp = lifecycles.getPostConstructCallbacks(); + + for (LifeCycleCallback c:tmp) + { + out.open("post-construct"); + out.tag("lifecycle-callback-class", c.getTargetClassName()); + out.tag("lifecycle-callback-method", c.getMethodName()); + out.close(); + } + + tmp = lifecycles.getPreDestroyCallbacks(); + for (LifeCycleCallback c:tmp) + { + out.open("pre-destroy"); + out.tag("lifecycle-callback-class", c.getTargetClassName()); + out.tag("lifecycle-callback-method", c.getMethodName()); + out.close(); + } + } + + out.literal(extraXML); + + out.close(); + } + + private void addContextParamFromAttribute(XmlAppendable out, String attribute) throws IOException + { + addContextParamFromAttribute(out,attribute,null); + } + + private void addContextParamFromAttribute(XmlAppendable out, String attribute, String resourceBase) throws IOException + { + Object o=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 "); + if (resourceBase==null) + QuotedStringTokenizer.quote(v,i.toString()); + else + QuotedStringTokenizer.quote(v,i.toString().replace(resourceBase,"${WAR}/")); + } + } + out.open("context-param") + .tag("param-name",attribute) + .tagCDATA("param-value",v.toString()) + .close(); + } + + private static void outholder(XmlAppendable out, MetaData md, String tag, Holder holder) throws IOException + { + out.open(tag,Collections.singletonMap("source",holder.getSource().toString())); + String n = holder.getName(); + out.tag(tag + "-name",n); + + String ot = n + "." + tag + "."; + + out.tag(tag + "-class",origin(md,ot + tag + "-class"),holder.getClassName()); + + for (String p : holder.getInitParameters().keySet()) + { + if ("scratchdir".equalsIgnoreCase(p)) //don't preconfigure the temp dir for jsp output + continue; + out.open("init-param",origin(md,ot + "init-param." + p)) + .tag("param-name",p) + .tag("param-value",holder.getInitParameter(p)) + .close(); + } + + if (holder instanceof ServletHolder) + { + ServletHolder s = (ServletHolder)holder; + if (s.getForcedPath() != null) + out.tag("jsp-file",s.getForcedPath()); + + if (s.getInitOrder() != 0) + out.tag("load-on-startup",Integer.toString(s.getInitOrder())); + + if (s.getRunAsRole() != null) + out.open("run-as",origin(md,ot + "run-as")) + .tag("role-name",s.getRunAsRole()) + .close(); + + Map roles = s.getRoleRefMap(); + if (roles!=null) + { + for (Map.Entry e : roles.entrySet()) + { + out.open("security-role-ref",origin(md,ot+"role-name."+e.getKey())) + .tag("role-name",e.getKey()) + .tag("role-link",e.getValue()) + .close(); + } + } + + if (!s.isEnabled()) + out.tag("enabled",origin(md,ot + "enabled"),"false"); + + //multipart-config + MultipartConfigElement multipartConfig = ((ServletHolder.Registration)s.getRegistration()).getMultipartConfig(); + if (multipartConfig != null) + { + out.open("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.close(); + } + } + + out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false"); + out.close(); + } + + public static Map origin(MetaData md, String name) + { + if (!LOG.isDebugEnabled()) + return Collections.emptyMap(); + if (name == null) + return Collections.emptyMap(); + OriginInfo origin = md.getOriginInfo(name); + // System.err.println("origin of "+name+" is "+origin); + if (origin == null) + return Collections.emptyMap(); + return Collections.singletonMap("origin",origin.toString()); + + } + +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java new file mode 100644 index 00000000000..570b724cae9 --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.File; +import java.io.FileInputStream; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class PreconfigureJNDIWar +{ + private static final long __start=System.nanoTime(); + private static final Logger LOG = Log.getLogger(Server.class); + + public static void main(String[] args) throws Exception + { + String target="target/test-jndi-preconfigured"; + File file = new File(target); + if (file.exists()) + IO.delete(file); + + PreconfigureQuickStartWar.main("target/test-jndi.war",target, "src/test/resources/test-jndi.xml"); + + LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start)); + + IO.copy(new FileInputStream("target/test-jndi-preconfigured/WEB-INF/quickstart-web.xml"),System.out); + } + +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java new file mode 100644 index 00000000000..f8b49c77c2a --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.File; +import java.io.FileInputStream; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +public class PreconfigureSpecWar +{ + private static final long __start=System.nanoTime(); + private static final Logger LOG = Log.getLogger(Server.class); + + public static void main(String[] args) throws Exception + { + String target="target/test-spec-preconfigured"; + File file = new File(target); + if (file.exists()) + IO.delete(file); + + File realmPropertiesDest = new File ("target/test-spec-realm.properties"); + if (realmPropertiesDest.exists()) + IO.delete(realmPropertiesDest); + + Resource realmPropertiesSrc = Resource.newResource("src/test/resources/realm.properties"); + realmPropertiesSrc.copyTo(realmPropertiesDest); + System.setProperty("jetty.home", "target"); + + PreconfigureQuickStartWar.main("target/test-spec.war",target, "src/test/resources/test-spec.xml"); + + LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start)); + + IO.copy(new FileInputStream("target/test-spec-preconfigured/WEB-INF/quickstart-web.xml"),System.out); + } + +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java new file mode 100644 index 00000000000..91e424165e1 --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java @@ -0,0 +1,62 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.File; +import java.io.FileInputStream; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +/** + * PreconfigureStandardTestWar + * + */ +public class PreconfigureStandardTestWar +{ + + private static final long __start=System.nanoTime(); + private static final Logger LOG = Log.getLogger(Server.class); + + public static void main(String[] args) throws Exception + { + String target="target/test-standard-preconfigured"; + File file = new File(target); + if (file.exists()) + IO.delete(file); + + File realmPropertiesDest = new File ("target/test-standard-realm.properties"); + if (realmPropertiesDest.exists()) + IO.delete(realmPropertiesDest); + + Resource realmPropertiesSrc = Resource.newResource("src/test/resources/realm.properties"); + realmPropertiesSrc.copyTo(realmPropertiesDest); + System.setProperty("jetty.home", "target"); + + PreconfigureQuickStartWar.main("target/test-standard.war",target, "src/test/resources/test.xml"); + + LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start)); + + IO.copy(new FileInputStream("target/test-standard-preconfigured/WEB-INF/quickstart-web.xml"),System.out); + } +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java new file mode 100644 index 00000000000..07bc1b66486 --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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; + +public class QuickStartJNDIWar +{ + + public static void main(String... args) throws Exception + { + // Log.getRootLogger().setDebugEnabled(true); + Quickstart.main("target/test-jndi-preconfigured", "src/test/resources/test-jndi.xml"); + } + + +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java new file mode 100644 index 00000000000..b84a12f2980 --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java @@ -0,0 +1,30 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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; + + +public class QuickStartSpecWar +{ + public static void main(String... args) throws Exception + { + // Log.getRootLogger().setDebugEnabled(true); + System.setProperty("jetty.home", "target"); + Quickstart.main("target/test-spec-preconfigured", "src/test/resources/test-spec.xml"); + } +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java new file mode 100644 index 00000000000..7da8b234653 --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java @@ -0,0 +1,30 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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; + +public class QuickStartStandardTestWar +{ + + public static void main(String... args) throws Exception + { + // Log.getRootLogger().setDebugEnabled(true); + System.setProperty("jetty.home", "target"); + Quickstart.main("target/test-standard-preconfigured", "src/test/resources/test.xml"); + } +} diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java new file mode 100644 index 00000000000..75dc114e10c --- /dev/null +++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java @@ -0,0 +1,73 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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 org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.xml.XmlConfiguration; + +public class Quickstart +{ + + public static void main(String... args) throws Exception + { + if (args.length<1) + error("No WAR file or directory given"); + + //war file or dir to start + String war = args[0]; + + //optional jetty context xml file to configure the webapp + Resource contextXml = null; + if (args.length > 1) + contextXml = Resource.newResource(args[1]); + + Server server = new Server(8080); + + QuickStartWebApp webapp = new QuickStartWebApp(); + webapp.setAutoPreconfigure(true); + webapp.setWar(war); + webapp.setContextPath("/"); + + //apply context xml file + if (contextXml != null) + { + // System.err.println("Applying "+contextXml); + XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXml.getURL()); + xmlConfiguration.configure(webapp); + } + + server.setHandler(webapp); + + server.start(); + + + + server.join(); + } + + + private static void error(String message) + { + System.err.println("ERROR: "+message); + System.err.println("Usage: java -jar QuickStartWar.jar "); + System.err.println(" java -jar QuickStartWar.jar "); + System.exit(1); + } +} diff --git a/jetty-quickstart/src/test/resources/realm.properties b/jetty-quickstart/src/test/resources/realm.properties new file mode 100644 index 00000000000..9d88b852b7f --- /dev/null +++ b/jetty-quickstart/src/test/resources/realm.properties @@ -0,0 +1,21 @@ +# +# This file defines users passwords and roles for a HashUserRealm +# +# The format is +# : [, ...] +# +# Passwords may be clear text, obfuscated or checksummed. The class +# org.eclipse.util.Password should be used to generate obfuscated +# passwords or password checksums +# +# If DIGEST Authentication is used, the password must be in a recoverable +# format, either plain text or OBF:. +# +jetty: MD5:164c88b302622e17050af52c89945d44,user +admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user +other: OBF:1xmk1w261u9r1w1c1xmq,user +plain: plain,user +user: password,user + +# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password +digest: MD5:6e120743ad67abfbc385bc2bb754e297,user diff --git a/jetty-quickstart/src/test/resources/test-jndi.xml b/jetty-quickstart/src/test/resources/test-jndi.xml new file mode 100644 index 00000000000..14c09348451 --- /dev/null +++ b/jetty-quickstart/src/test/resources/test-jndi.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + woggle + 4000 + false + + + + + + wiggle + 100 + true + + + + + + mail/Session + + + CHANGE-ME + CHANGE-ME + + + false + CHANGE-ME + CHANGE-ME + false + + + + + + + + + + jdbc/mydatasource + + + + + + diff --git a/jetty-quickstart/src/test/resources/test-spec.xml b/jetty-quickstart/src/test/resources/test-spec.xml new file mode 100644 index 00000000000..99fc577205e --- /dev/null +++ b/jetty-quickstart/src/test/resources/test-spec.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + Test Realm + /test-spec-realm.properties + + + + + + + + maxAmount + 100 + true + + + + + + jdbc/mydatasource + + + + + + + diff --git a/jetty-quickstart/src/test/resources/test.xml b/jetty-quickstart/src/test/resources/test.xml new file mode 100644 index 00000000000..bbdf08a23a9 --- /dev/null +++ b/jetty-quickstart/src/test/resources/test.xml @@ -0,0 +1,45 @@ + + + + + + + + /test + + + + + Test Realm + /test-standard-realm.properties + + + + + true + + + true + + + + + + + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java index 55966d7ab0e..7d998b39de5 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java @@ -147,23 +147,19 @@ public class Log } private static Logger LOG; - private static boolean __initialized; - - public static boolean initialized() - { - if (LOG != null) - { - return true; - } + private static boolean __initialized=false; + public static void initialized() + { + synchronized (Log.class) { if (__initialized) - { - return LOG != null; - } + return; __initialized = true; } + + final long uptime=ManagementFactory.getRuntimeMXBean().getUptime(); try { @@ -181,9 +177,7 @@ public class Log } if (LOG!=null) - LOG.info(String.format("Logging initialized @%dms",ManagementFactory.getRuntimeMXBean().getUptime())); - - return LOG != null; + LOG.info(String.format("Logging initialized @%dms",uptime)); } private static void initStandardLogging(Throwable e) @@ -284,12 +278,7 @@ public class Log */ public static Logger getLogger(String name) { - if (!initialized()) - { - IllegalStateException e = new IllegalStateException(); - e.printStackTrace(); - throw e; - } + initialized(); if(name==null) return LOG; diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java index 2d32f033e22..3fde5536e15 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java @@ -334,6 +334,11 @@ public class MetaData { _descriptorProcessors.add(p); } + + public void removeDescriptorProcessor(DescriptorProcessor p) + { + _descriptorProcessors.remove(p); + } public void orderFragments () diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index d0d65f2856a..aceff2017ed 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -115,6 +115,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL "org.eclipse.jetty.jndi.", // webapp cannot change naming classes "org.eclipse.jetty.jaas.", // webapp cannot change jaas classes "org.eclipse.jetty.websocket.", // webapp cannot change / replace websocket classes + "org.eclipse.jetty.util.log.", // webapp should use server log "org.eclipse.jetty.servlet.DefaultServlet", // webapp cannot change default servlets "org.eclipse.jetty.servlets.AsyncGzipFilter" // special case for AsyncGzipFilter } ; @@ -132,7 +133,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners "-org.eclipse.jetty.websocket.", // don't hide websocket classes from webapps (allow webapp to use ones from system classloader) - "-org.eclipse.jetty.apache.", // don't jetty apache impls + "-org.eclipse.jetty.apache.", // don't hide jetty apache impls + "-org.eclipse.jetty.util.log.", // don't hide server log "org.eclipse.jetty." // hide other jetty classes } ; @@ -943,7 +945,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL */ public void setConfigurationClasses(String[] configurations) { - if (isRunning()) + if (isStarted()) throw new IllegalStateException(); _configurationClasses.clear(); if (configurations!=null) @@ -962,7 +964,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL */ public void setConfigurations(Configuration[] configurations) { - if (isRunning()) + if (isStarted()) throw new IllegalStateException(); _configurations.clear(); if (configurations!=null) @@ -1345,9 +1347,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL //resolve the metadata _metadata.resolve(this); + startWebapp(); + } + + /* ------------------------------------------------------------ */ + protected void startWebapp() + throws Exception + { super.startContext(); } - + /* ------------------------------------------------------------ */ @Override public Set setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement) diff --git a/pom.xml b/pom.xml index bc48669b709..fd7ddc0666f 100644 --- a/pom.xml +++ b/pom.xml @@ -436,6 +436,7 @@ jetty-osgi jetty-rewrite jetty-nosql + jetty-quickstart tests examples jetty-distribution