Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--VERSION.txt70
-rw-r--r--jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java42
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java34
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java11
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java4
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java41
-rw-r--r--pom.xml16
-rw-r--r--tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java874
8 files changed, 1051 insertions, 41 deletions
diff --git a/VERSION.txt b/VERSION.txt
index d53dba269d..8a973b743f 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -108,7 +108,6 @@ jetty-9.0.4.v20130625 - 25 June 2013
+ 410405 Avoid NPE for requestDispatcher(../)
+ 410469 UpgradeRequest is sent twice when using SSL, one fails warning about
WritePendingException
- + 410498 ignore type of exception in GoAwayTest.testDataNotProcessedAfterGoAway
+ 410522 jetty start broken for command line options
+ 410537 Exceptions during @OnWebSocketConnect not reported to
@OnWebSocketError
@@ -301,6 +300,75 @@ jetty-9.0.0.v20130308 - 08 March 2013
+ 402757 WebSocket client module can't be used with WebSocket server module in
the same WAR
+jetty-8.1.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+ running
+ + 408806 getParameter returns null on Multipart request if called before
+ request.getPart()/getParts()
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409436 NPE on context restart using dynamic servlet registration
+ + 409449 Ensure servlets, filters and listeners added via dynamic
+ registration, annotations or descriptors are cleaned on context restarts
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410405 Avoid NPE for requestDispatcher(../)
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+ server restarts
+ + 410893 async support defaults to false for spec created servlets and filters
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+ the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+ parameters 411459 MultiPartFilter.Wrapper getParameter should use charset
+ encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
+jetty-7.6.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+ running
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+ server restarts
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+ the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+ parameters 411459 MultiPartFilter.Wrapper getParameter should use charset
+ encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
jetty-8.1.11.v20130520 - 20 May 2013
+ 402844 STOP.PORT & STOP.KEY behaviour has changed
+ 403281 jetty.sh waits for started or failure before returning
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
index 45561d4f42..028e4f269d 100644
--- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
@@ -20,10 +20,10 @@ package org.eclipse.jetty.jaas.spi;
import java.security.Principal;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
@@ -45,7 +45,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule
private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class);
- private static Map<String, PropertyUserStore> _propertyUserStores = new HashMap<String, PropertyUserStore>();
+ private static ConcurrentHashMap<String, PropertyUserStore> _propertyUserStores = new ConcurrentHashMap<String, PropertyUserStore>();
private int _refreshInterval = 0;
private String _filename = DEFAULT_FILENAME;
@@ -68,33 +68,37 @@ public class PropertyFileLoginModule extends AbstractLoginModule
private void setupPropertyUserStore(Map<String, ?> options)
{
+ parseConfig(options);
+
if (_propertyUserStores.get(_filename) == null)
{
- parseConfig(options);
-
- PropertyUserStore _propertyUserStore = new PropertyUserStore();
- _propertyUserStore.setConfig(_filename);
- _propertyUserStore.setRefreshInterval(_refreshInterval);
- LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
+ PropertyUserStore propertyUserStore = new PropertyUserStore();
+ propertyUserStore.setConfig(_filename);
+ propertyUserStore.setRefreshInterval(_refreshInterval);
- try
+ PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore);
+ if (prev == null)
{
- _propertyUserStore.start();
+ LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
+
+ try
+ {
+ propertyUserStore.start();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Exception while starting propertyUserStore: ",e);
+ }
}
- catch (Exception e)
- {
- LOG.warn("Exception while starting propertyUserStore: ",e);
- }
-
- _propertyUserStores.put(_filename,_propertyUserStore);
}
}
private void parseConfig(Map<String, ?> options)
{
- _filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME;
- String refreshIntervalString = (String)options.get("refreshInterval");
- _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString);
+ String tmp = (String)options.get("file");
+ _filename = (tmp == null? DEFAULT_FILENAME : tmp);
+ tmp = (String)options.get("refreshInterval");
+ _refreshInterval = (tmp == null?_refreshInterval:Integer.parseInt(tmp));
}
/**
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
index 7d70908618..9098ea3bcf 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
@@ -703,7 +703,6 @@ public class MultipartFilterTest
assertTrue(response.getContent().contains("aaaa,bbbbb"));
}
- @Test
public void testContentTypeWithCharSet() throws Exception
{
// generated and parsed test
@@ -733,6 +732,39 @@ public class MultipartFilterTest
assertTrue(response.getContent().indexOf("brown cow")>=0);
}
+
+ @Test
+ public void testBufferOverflowNoCRLF () throws Exception
+ {
+ String boundary="XyXyXy";
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+ tester.addServlet(BoundaryServlet.class,"/testb");
+ tester.setAttribute("fileName", "abc");
+ tester.setAttribute("desc", "123");
+ tester.setAttribute("title", "ttt");
+ request.setMethod("POST");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host","tester");
+ request.setURI("/context/testb");
+ request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
+
+ String content = "--XyXyXy";
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write(content.getBytes());
+
+ for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
+ {
+ baos.write('a');
+ }
+ request.setContent(baos.toString());
+
+ response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+ assertTrue(response.getContent().contains("Buffer size exceeded"));
+ assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
+ }
/*
* see the testParameterMap test
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
index 5b9717d311..ee29487d40 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
@@ -488,7 +488,16 @@ public class MultiPartInputStreamParser
byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
// Get first boundary
- String line=((ReadLineInputStream)_in).readLine();
+ String line = null;
+ try
+ {
+ line=((ReadLineInputStream)_in).readLine();
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Badly formatted multipart request");
+ throw e;
+ }
if (line == null)
throw new IOException("Missing content for multipart request");
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
index bb4c9fbc87..1aeec5fbb6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
@@ -49,6 +49,10 @@ public class ReadLineInputStream extends BufferedInputStream
while (true)
{
int b=super.read();
+
+ if (markpos < 0)
+ throw new IOException("Buffer size exceeded: no line terminator");
+
if (b==-1)
{
int m=markpos;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
index b3f50cee0c..e096b24e23 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
@@ -18,17 +18,8 @@
package org.eclipse.jetty.util;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -530,6 +521,34 @@ public class MultiPartInputStreamTest
}
@Test
+ public void testBufferOverflowNoCRLF () throws Exception
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write("--AaB03x".getBytes());
+ for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
+ {
+ baos.write('a');
+ }
+
+ MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+ MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()),
+ _contentType,
+ config,
+ _tmpDir);
+ mpis.setDeleteOnExit(true);
+ try
+ {
+ mpis.getParts();
+ fail ("Multipart buffer overrun");
+ }
+ catch (IOException e)
+ {
+ assertTrue(e.getMessage().startsWith("Buffer size exceeded"));
+ }
+
+ }
+
+
public void testCharsetEncoding () throws Exception
{
String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1";
diff --git a/pom.xml b/pom.xml
index 45e04b3929..43202cae52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,7 +137,7 @@
</configuration>
</execution>
<execution>
- <id>ban-junit.jar</id>
+ <id>ban-junit-dep.jar</id>
<goals>
<goal>enforce</goal>
</goals>
@@ -145,10 +145,10 @@
<rules>
<bannedDependencies>
<excludes>
- <exclude>junit:junit:*:jar</exclude>
+ <exclude>junit:junit-dep:*:jar</exclude>
</excludes>
<searchTransitive>true</searchTransitive>
- <message>We use junit-dep.jar, not junit.jar (as the standard junit.jar aggregates too many 3rd party libs inside of it)</message>
+ <message>We use junit.jar, not junit-dep.jar (as of junit 4.11, hamcrest is no longer embedded)</message>
</bannedDependencies>
</rules>
</configuration>
@@ -553,7 +553,7 @@
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
- <version>2.3</version>
+ <version>2.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -575,7 +575,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.8.1</version>
+ <version>4.11</version>
</dependency>
-->
<dependency>
@@ -586,17 +586,17 @@
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
- <version>1.2.1</version>
+ <version>1.3</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
- <version>1.2.1</version>
+ <version>1.3</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>1.8.5</version>
+ <version>1.9.5</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java
new file mode 100644
index 0000000000..99ce5ffdc2
--- /dev/null
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java
@@ -0,0 +1,874 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.test.support;
+//
+//========================================================================
+//------------------------------------------------------------------------
+//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.
+//========================================================================
+//
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.JAR;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.PathAssert;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+
+/**
+ * Basic process based executor for using the Jetty Distribution along with custom configurations to perform basic
+ * <p>
+ * Allows for a test specific directory, that is a copied jetty-distribution, and then modified for the test specific testing required.
+ * <p>
+ * Requires that you setup the maven-dependency-plugin appropriately for the base distribution you want to use, along with any other dependencies (wars, libs,
+ * etc..) that you may need from other maven projects.
+ * <p>
+ * Maven Dependency Plugin Setup:
+ *
+ * <pre>
+ * &lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;
+ *
+ * &lt;!-- Common Destination Directories --&gt;
+ *
+ * &lt;properties&gt;
+ * &lt;test-wars-dir&gt;${project.build.directory}/test-wars&lt;/test-wars-dir&gt;
+ * &lt;test-libs-dir&gt;${project.build.directory}/test-libs&lt;/test-libs-dir&gt;
+ * &lt;test-distro-dir&gt;${project.build.directory}/test-dist&lt;/test-distro-dir&gt;
+ * &lt;/properties&gt;
+ *
+ * &lt;build&gt;
+ * &lt;plugins&gt;
+ * &lt;plugin&gt;
+ * &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
+ * &lt;artifactId&gt;maven-dependency-plugin&lt;/artifactId&gt;
+ * &lt;version&gt;2.1&lt;/version&gt;
+ * &lt;executions&gt;
+ *
+ * &lt;!-- Copy LIB and WAR dependencies into place that JettyDistro can use them --&gt;
+ *
+ * &lt;execution&gt;
+ * &lt;id&gt;test-lib-war-copy&lt;/id&gt;
+ * &lt;phase&gt;process-test-resources&lt;/phase&gt;
+ * &lt;goals&gt;
+ * &lt;goal&gt;copy&lt;/goal&gt;
+ * &lt;/goals&gt;
+ * &lt;configuration&gt;
+ * &lt;artifactItems&gt;
+ * &lt;artifactItem&gt;
+ * &lt;groupId&gt;org.mortbay.jetty.testwars&lt;/groupId&gt;
+ * &lt;artifactId&gt;test-war-java_util_logging&lt;/artifactId&gt;
+ * &lt;version&gt;7.3.0&lt;/version&gt;
+ * &lt;type&gt;war&lt;/type&gt;
+ * &lt;outputDirectory&gt;${test-wars-dir}&lt;/outputDirectory&gt;
+ * &lt;/artifactItem&gt;
+ * &lt;artifactItem&gt;
+ * &lt;groupId&gt;org.mortbay.jetty&lt;/groupId&gt;
+ * &lt;artifactId&gt;jetty-aspect-servlet-api-2.5&lt;/artifactId&gt;
+ * &lt;version&gt;7.3.0&lt;/version&gt;
+ * &lt;type&gt;jar&lt;/type&gt;
+ * &lt;outputDirectory&gt;${test-libs-dir}&lt;/outputDirectory&gt;
+ * &lt;/artifactItem&gt;
+ * &lt;/artifactItems&gt;
+ * &lt;overWriteIfNewer&gt;true&lt;/overWriteIfNewer&gt;
+ * &lt;overWrite&gt;true&lt;/overWrite&gt;
+ * &lt;stripVersion&gt;true&lt;/stripVersion&gt;
+ * &lt;/configuration&gt;
+ * &lt;/execution&gt;
+ *
+ * &lt;!-- Extract Jetty DISTRIBUTION into place that JettyDistro can use it --&gt;
+ *
+ * &lt;execution&gt;
+ * &lt;id&gt;unpack-test-dist&lt;/id&gt;
+ * &lt;phase&gt;process-test-resources&lt;/phase&gt;
+ * &lt;goals&gt;
+ * &lt;goal&gt;unpack&lt;/goal&gt;
+ * &lt;/goals&gt;
+ * &lt;configuration&gt;
+ * &lt;artifactItems&gt;
+ * &lt;artifactItem&gt;
+ * &lt;groupId&gt;org.eclipse.jetty&lt;/groupId&gt;
+ * &lt;artifactId&gt;jetty-distribution&lt;/artifactId&gt;
+ * &lt;version&gt;7.3.0&lt;/version&gt;
+ * &lt;type&gt;zip&lt;/type&gt;
+ * &lt;overWrite&gt;true&lt;/overWrite&gt;
+ * &lt;/artifactItem&gt;
+ * &lt;/artifactItems&gt;
+ * &lt;outputAbsoluteArtifactFilename&gt;true&lt;/outputAbsoluteArtifactFilename&gt;
+ * &lt;outputDirectory&gt;${test-distro-dir}&lt;/outputDirectory&gt;
+ * &lt;overWriteSnapshots&gt;true&lt;/overWriteSnapshots&gt;
+ * &lt;overWriteIfNewer&gt;true&lt;/overWriteIfNewer&gt;
+ * &lt;/configuration&gt;
+ * &lt;/execution&gt;
+ * &lt;/executions&gt;
+ * &lt;/plugin&gt;
+ * &lt;/plugins&gt;
+ * &lt;/build&gt;
+ *
+ * &lt;/project&gt;
+ * </pre>
+ * <p>
+ * If you have a specific configuration you want to setup, you'll want to prepare this configuration in an overlay directory underneath the
+ * <code>src/test/resources/</code> directory. <br>
+ * Notes:
+ * <ol>
+ * <li>The {@link JettyDistro} sets up a unique test directory (based on the constructor {@link #JettyDistro(Class)} or {@link #JettyDistro(TestingDir)}), by
+ * ensuring the directory is empty, then copying the <code>target/test-dist</code> directory into this new testing directory prior to the test specific changes
+ * to the configuration.<br>
+ * Note: this testing directory is a complete jetty distribution, suitable for executing via the command line for additional testing needs.</li>
+ * <li>The directory name you choose in <code>src/test/resources</code> will be the name you use in the {@link #overlayConfig(String)} method to provide
+ * replacement configurations for the Jetty Distribution.</li>
+ * <li>You'll want to {@link #delete(String)} any files and/or directories from the standard distribution prior to using the {@link #overlayConfig(String)}
+ * method.</li>
+ * <li>Use the {@link #copyLib(String, String)} method to copy JAR files from the <code>target/test-libs</code> directory (created and managed above using the
+ * <code>maven-dependency-plugin</code>) to copy the lib into the test specific.</li>
+ * <li>Use the {@link #copyTestWar(String)} method to copy WAR files from the <code>target/test-wars</code> directory (created and managed above using the
+ * <code>maven-dependency-plugin</code>) to copy the WAR into the test specific directory.</li>
+ * </ol>
+ * <p>
+ * Next you'll want to use Junit 4.8+ and the <code>&#064;BeforeClass</code> and <code>&#064;AfterClass</code> annotations to setup the <code>JettyDistro</code>
+ * class for setting up your testing configuration.
+ * <p>
+ * Example Test Case using {@link JettyDistro} class
+ *
+ * <pre>
+ * public class MySampleTest
+ * {
+ * private static JettyDistro jetty;
+ *
+ * &#064;BeforeClass
+ * public static void initJetty() throws Exception
+ * {
+ * jetty = new JettyDistro(MySampleTest.class);
+ *
+ * jetty.copyTestWar(&quot;test-war-java_util_logging.war&quot;);
+ * jetty.copyTestWar(&quot;test-war-policy.war&quot;);
+ *
+ * jetty.delete(&quot;webapps/test.war&quot;);
+ * jetty.delete(&quot;contexts/test.d&quot;);
+ * jetty.delete(&quot;contexts/javadoc.xml&quot;);
+ * jetty.delete(&quot;contexts/test.xml&quot;);
+ *
+ * jetty.overlayConfig(&quot;no_security&quot;);
+ *
+ * jetty.setDebug(true);
+ *
+ * jetty.start();
+ * }
+ *
+ * &#064;AfterClass
+ * public static void shutdownJetty() throws Exception
+ * {
+ * if (jetty != null)
+ * {
+ * jetty.stop();
+ * }
+ * }
+ *
+ * &#064;Test
+ * public void testRequest() throws Exception
+ * {
+ * SimpleRequest request = new SimpleRequest(jetty.getBaseUri());
+ * String path = &quot;/test-war-policy/security/PRACTICAL/testFilsystem&quot;);
+ * String response = request.getString(path);
+ * Assert.assertEquals(&quot;Success&quot;, response);
+ * }
+ * }
+ * </pre>
+ */
+public class JettyDistro
+{
+ private String artifactName = "jetty-distribution";
+ private long startTime = 60;
+ private TimeUnit timeUnit = TimeUnit.SECONDS;
+
+ private File jettyHomeDir;
+ private Process pid;
+ private URI baseUri;
+
+ private String jmxUrl;
+
+ private boolean _debug = false;
+
+ /**
+ * Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
+ *
+ * @param clazz
+ * the testing class using this JettyDistro
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(Class<?> clazz) throws IOException
+ {
+ this(clazz,null);
+ }
+
+ /**
+ * Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
+ *
+ * @param clazz
+ * the testing class using this JettyDistro
+ * @param artifact
+ * name of jetty distribution artifact
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(Class<?> clazz, String artifact) throws IOException
+ {
+ this.jettyHomeDir = MavenTestingUtils.getTargetTestingDir(clazz,"jettyHome");
+ if (artifact != null)
+ {
+ this.artifactName = artifact;
+ }
+
+ copyBaseDistro();
+ }
+
+ /**
+ * Setup the JettyHome as belonging to a specific testing method directory
+ *
+ * @param testdir
+ * the testing directory to use as the JettyHome for this JettyDistro
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(TestingDir testdir) throws IOException
+ {
+ this.jettyHomeDir = testdir.getDir();
+ copyBaseDistro();
+ }
+
+ /**
+ * Setup the JettyHome as belonging to a specific testing method directory
+ *
+ * @param testdir
+ * the testing directory to use as the JettyHome for this JettyDistro
+ * @param artifact
+ * name of jetty distribution artifact
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(TestingDir testdir, String artifact) throws IOException
+ {
+ this.jettyHomeDir = testdir.getDir();
+ if (artifact != null)
+ {
+ this.artifactName = artifact;
+ }
+
+ copyBaseDistro();
+ }
+
+ /**
+ *
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ private void copyBaseDistro() throws IOException
+ {
+ // The outputDirectory for the maven side dependency:unpack goal.
+ File distroUnpackDir = MavenTestingUtils.getTargetFile("test-dist");
+ PathAssert.assertDirExists(artifactName + " dependency:unpack",distroUnpackDir);
+
+ // The actual jetty-distribution-${version} directory is under this directory.
+ // Lets find it.
+ File subdirs[] = distroUnpackDir.listFiles(new FileFilter()
+ {
+ public boolean accept(File path)
+ {
+ if (!path.isDirectory())
+ {
+ return false;
+ }
+
+ return path.getName().startsWith(artifactName + "-");
+ }
+ });
+
+ if (subdirs.length == 0)
+ {
+ // No jetty-distribution found.
+ StringBuilder err = new StringBuilder();
+ err.append("No target/test-dist/");
+ err.append(artifactName);
+ err.append("-${version} directory found.");
+ err.append("\n To fix this, run 'mvn process-test-resources' to create the directory.");
+ throw new IOException(err.toString());
+ }
+
+ if (subdirs.length != 1)
+ {
+ // Too many jetty-distributions found.
+ StringBuilder err = new StringBuilder();
+ err.append("Too many target/test-dist/");
+ err.append(artifactName);
+ err.append("-${version} directories found.");
+ for (File dir : subdirs)
+ {
+ err.append("\n ").append(dir.getAbsolutePath());
+ }
+ err.append("\n To fix this, run 'mvn clean process-test-resources' to recreate the target/test-dist directory.");
+ throw new IOException(err.toString());
+ }
+
+ File distroSrcDir = subdirs[0];
+ FS.ensureEmpty(jettyHomeDir);
+ System.out.printf("Copying Jetty Distribution: %s%n",distroSrcDir.getAbsolutePath());
+ System.out.printf(" To Testing Dir: %s%n",jettyHomeDir.getAbsolutePath());
+ IO.copyDir(distroSrcDir,jettyHomeDir);
+ }
+
+ /**
+ * Return the $(jetty.home) directory being used for this JettyDistro
+ *
+ * @return the jetty.home directory being used
+ */
+ public File getJettyHomeDir()
+ {
+ return this.jettyHomeDir;
+ }
+
+ /**
+ * Copy a war file from ${project.basedir}/target/test-wars/${testWarFilename} into the ${jetty.home}/webapps/ directory
+ *
+ * @param testWarFilename
+ * the war file to copy (must exist)
+ * @throws IOException
+ * if unable to copy the war file.
+ */
+ public void copyTestWar(String testWarFilename) throws IOException
+ {
+ File srcWar = MavenTestingUtils.getTargetFile("test-wars/" + testWarFilename);
+ File destWar = new File(jettyHomeDir,OS.separators("webapps/" + testWarFilename));
+ FS.ensureDirExists(destWar.getParentFile());
+ IO.copyFile(srcWar,destWar);
+ }
+
+ /**
+ * Copy an arbitrary file from <code>src/test/resources/${resourcePath}</code> to the testing directory.
+ *
+ * @param resourcePath
+ * the relative path for file content within the <code>src/test/resources</code> directory.
+ * @param outputPath
+ * the testing directory relative output path for the file output (will result in a file with the outputPath name being created)
+ * @throws IOException
+ * if unable to copy resource file
+ */
+ public void copyResource(String resourcePath, String outputPath) throws IOException
+ {
+ File srcFile = MavenTestingUtils.getTestResourceFile(resourcePath);
+ File destFile = new File(jettyHomeDir,OS.separators(outputPath));
+ FS.ensureDirExists(destFile.getParentFile());
+ IO.copyFile(srcFile,destFile);
+ }
+
+ /**
+ * Copy an arbitrary file from <code>target/test-libs/${libFilename}</code> to the testing directory.
+ *
+ * @param libFilename
+ * the <code>target/test-libs/${libFilename}</code> to copy
+ * @param outputPath
+ * the destination testing directory relative output path for the lib. (will result in a file with the outputPath name being created)
+ * @throws IOException
+ * if unable to copy lib
+ */
+ public void copyLib(String libFilename, String outputPath) throws IOException
+ {
+ File srcLib = MavenTestingUtils.getTargetFile("test-libs/" + libFilename);
+ File destLib = new File(jettyHomeDir,OS.separators(outputPath));
+ FS.ensureDirExists(destLib.getParentFile());
+ IO.copyFile(srcLib,destLib);
+ }
+
+ /**
+ * Copy the <code>${project.basedir}/src/main/config/</code> tree into the testing directory.
+ *
+ * @throws IOException
+ * if unable to copy the directory tree
+ */
+ public void copyProjectMainConfig() throws IOException
+ {
+ File srcDir = MavenTestingUtils.getProjectDir("src/main/config");
+ IO.copyDir(srcDir,jettyHomeDir);
+ }
+
+ /**
+ * Create a <code>${jetty.home}/lib/self/${jarFilename}</code> jar file from the content in the <code>${project.basedir}/target/classes/</code> directory.
+ *
+ * @throws IOException
+ * if unable to copy the directory tree
+ */
+ public void createProjectLib(String jarFilename) throws IOException
+ {
+ File srcDir = MavenTestingUtils.getTargetFile("classes");
+ File libSelfDir = new File(jettyHomeDir,OS.separators("lib/self"));
+ FS.ensureDirExists(libSelfDir);
+ File jarFile = new File(libSelfDir,jarFilename);
+ JAR.create(srcDir,jarFile);
+ }
+
+ /**
+ * Unpack an arbitrary config from <code>target/test-configs/${configFilename}</code> to the testing directory.
+ *
+ * @param configFilename
+ * the <code>target/test-configs/${configFilename}</code> to copy
+ * @throws IOException
+ * if unable to unpack config file
+ */
+ public void unpackConfig(String configFilename) throws IOException
+ {
+ File srcConfig = MavenTestingUtils.getTargetFile("test-configs/" + configFilename);
+ JAR.unpack(srcConfig,jettyHomeDir);
+ }
+
+ /**
+ * Delete a File or Directory found in the ${jetty.home} directory.
+ *
+ * @param path
+ * the path to delete. (can be a file or directory)
+ */
+ public void delete(String path)
+ {
+ File jettyPath = new File(jettyHomeDir,OS.separators(path));
+ FS.delete(jettyPath);
+ }
+
+ /**
+ * Return the baseUri being used for this Jetty Process Instance.
+ *
+ * @return the base URI for this Jetty Process Instance.
+ */
+ public URI getBaseUri()
+ {
+ return this.baseUri;
+ }
+
+ /**
+ * Return the JMX URL being used for this Jetty Process Instance.
+ *
+ * @return the JMX URL for this Jetty Process Instance.
+ */
+ public String getJmxUrl()
+ {
+ return this.jmxUrl;
+ }
+
+ /**
+ * Take the directory contents from ${project.basedir}/src/test/resources/${testConfigName}/ and copy it over whatever happens to be at ${jetty.home}
+ *
+ * @param testConfigName
+ * the src/test/resources/ directory name to use as the source diretory for the configuration we are interested in.
+ * @throws IOException
+ * if unable to copy directory.
+ */
+ public void overlayConfig(String testConfigName) throws IOException
+ {
+ File srcDir = MavenTestingUtils.getTestResourceDir(testConfigName);
+ IO.copyDir(srcDir,jettyHomeDir);
+ }
+
+ /**
+ * Start the jetty server
+ *
+ * @throws IOException
+ * if unable to start the server.
+ */
+ public void start() throws IOException
+ {
+ List<String> commands = new ArrayList<String>();
+ commands.add(getJavaBin());
+
+ commands.add("-Djetty.home=" + jettyHomeDir.getAbsolutePath());
+
+ // Do a dry run first to get the exact command line for Jetty process
+ commands.add("-jar");
+ commands.add("start.jar");
+ commands.add("jetty.port=0");
+ if (_debug)
+ {
+ commands.add("-D.DEBUG=true");
+ }
+ commands.add("--dry-run");
+
+ ProcessBuilder pbCmd = new ProcessBuilder(commands);
+ pbCmd.directory(jettyHomeDir);
+
+ String cmdLine = null;
+ Process pidCmd = pbCmd.start();
+ try
+ {
+ cmdLine = readOutputLine(pidCmd);
+ }
+ finally
+ {
+ pidCmd.destroy();
+ }
+
+ if (cmdLine == null || !cmdLine.contains("XmlConfiguration"))
+ {
+ Assert.fail("Unable to get Jetty command line");
+ }
+
+ // Need to breakdown commandline into parts, as spaces in command line will cause failures.
+ List<String> execCommands = splitAndUnescapeCommandLine(cmdLine);
+
+ System.out.printf("Executing: %s%n",cmdLine);
+ System.out.printf("Working Dir: %s%n",jettyHomeDir.getAbsolutePath());
+
+ pbCmd = new ProcessBuilder(execCommands);
+ pid = pbCmd.start();
+
+ ConsoleParser parser = new ConsoleParser();
+ List<String[]> jmxList = parser.newPattern("JMX Remote URL: (.*)",0);
+ List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)",1);
+ // DISABLED: This is what exists in Jetty 9+
+ // List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@.*[\\({]([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)[\\)}].*",1);
+
+ startPump("STDOUT",parser,this.pid.getInputStream());
+ startPump("STDERR",parser,this.pid.getErrorStream());
+
+ try
+ {
+ parser.waitForDone(this.startTime,this.timeUnit);
+
+ if (!jmxList.isEmpty())
+ {
+ this.jmxUrl = jmxList.get(0)[0];
+ System.out.printf("## Found JMX connector at %s%n",this.jmxUrl);
+ }
+
+ if (!connList.isEmpty())
+ {
+ String[] params = connList.get(0);
+ if (params.length == 2)
+ {
+ this.baseUri = URI.create("http://localhost:" + params[1] + "/");
+ }
+ System.out.printf("## Found Jetty connector at host: %s port: %s%n",(Object[])params);
+ }
+
+ }
+ catch (InterruptedException e)
+ {
+ pid.destroy();
+ Assert.fail("Unable to get required information within time limit");
+ }
+ }
+
+ public static List<String> splitAndUnescapeCommandLine(CharSequence rawCmdLine)
+ {
+ List<String> cmds = new ArrayList<String>();
+
+ int len = rawCmdLine.length();
+ StringBuilder arg = new StringBuilder();
+ boolean escaped = false;
+ boolean inQuote = false;
+ char c;
+ for (int i = 0; i < len; i++)
+ {
+ c = rawCmdLine.charAt(i);
+ if (escaped)
+ {
+ switch (c)
+ {
+ case 'r':
+ arg.append('\r');
+ break;
+ case 'f':
+ arg.append('\f');
+ break;
+ case 't':
+ arg.append('\t');
+ break;
+ case 'n':
+ arg.append('\n');
+ break;
+ case 'b':
+ arg.append('\b');
+ break;
+ default:
+ arg.append(c);
+ break;
+ }
+ escaped = false;
+ continue;
+ }
+
+ if (c == '\\')
+ {
+ escaped = true;
+ }
+ else
+ {
+ if ((c == ' ') && (!inQuote))
+ {
+ // the delim!
+ cmds.add(String.valueOf(arg.toString()));
+ arg.setLength(0);
+ }
+ else if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else
+ {
+ arg.append(c);
+ }
+ }
+ }
+ cmds.add(String.valueOf(arg.toString()));
+
+ return cmds;
+ }
+
+ private String readOutputLine(Process pidCmd) throws IOException
+ {
+ InputStream in = null;
+ InputStreamReader reader = null;
+ BufferedReader buf = null;
+ try
+ {
+ in = pidCmd.getInputStream();
+ reader = new InputStreamReader(in);
+ buf = new BufferedReader(reader);
+ return buf.readLine();
+ }
+ finally
+ {
+ IO.close(buf);
+ IO.close(reader);
+ IO.close(in);
+ }
+ }
+
+ private static class ConsoleParser
+ {
+ private List<ConsolePattern> patterns = new ArrayList<ConsolePattern>();
+ private CountDownLatch latch;
+ private int count;
+
+ public List<String[]> newPattern(String exp, int cnt)
+ {
+ ConsolePattern pat = new ConsolePattern(exp,cnt);
+ patterns.add(pat);
+ count += cnt;
+
+ return pat.getMatches();
+ }
+
+ public void parse(String line)
+ {
+ for (ConsolePattern pat : patterns)
+ {
+ Matcher mat = pat.getMatcher(line);
+ if (mat.find())
+ {
+ int num = 0, count = mat.groupCount();
+ String[] match = new String[count];
+ while (num++ < count)
+ {
+ match[num - 1] = mat.group(num);
+ }
+ pat.getMatches().add(match);
+
+ if (pat.getCount() > 0)
+ {
+ getLatch().countDown();
+ }
+ }
+ }
+ }
+
+ public void waitForDone(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ getLatch().await(timeout,unit);
+ }
+
+ private CountDownLatch getLatch()
+ {
+ synchronized (this)
+ {
+ if (latch == null)
+ {
+ latch = new CountDownLatch(count);
+ }
+ }
+
+ return latch;
+ }
+ }
+
+ private static class ConsolePattern
+ {
+ private Pattern pattern;
+ private List<String[]> matches;
+ private int count;
+
+ ConsolePattern(String exp, int cnt)
+ {
+ pattern = Pattern.compile(exp);
+ matches = new ArrayList<String[]>();
+ count = cnt;
+ }
+
+ public Matcher getMatcher(String line)
+ {
+ return pattern.matcher(line);
+ }
+
+ public List<String[]> getMatches()
+ {
+ return matches;
+ }
+
+ public int getCount()
+ {
+ return count;
+ }
+ }
+
+ private void startPump(String mode, ConsoleParser parser, InputStream inputStream)
+ {
+ ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
+ pump.setParser(parser);
+ Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
+ thread.start();
+ }
+
+ /**
+ * enable debug on the jetty process
+ *
+ * @param debug
+ */
+ public void setDebug(boolean debug)
+ {
+ _debug = debug;
+ }
+
+ private String getJavaBin()
+ {
+ String javaexes[] = new String[]
+ { "java", "java.exe" };
+
+ File javaHomeDir = new File(System.getProperty("java.home"));
+ for (String javaexe : javaexes)
+ {
+ File javabin = new File(javaHomeDir,OS.separators("bin/" + javaexe));
+ if (javabin.exists() && javabin.isFile())
+ {
+ return javabin.getAbsolutePath();
+ }
+ }
+
+ Assert.fail("Unable to find java bin");
+ return "java";
+ }
+
+ /**
+ * Stop the jetty server
+ */
+ public void stop()
+ {
+ System.out.println("Stopping JettyDistro ...");
+ if (pid != null)
+ {
+ // TODO: maybe issue a STOP instead?
+ pid.destroy();
+ }
+ }
+
+ /**
+ * Simple streamer for the console output from a Process
+ */
+ private static class ConsoleStreamer implements Runnable
+ {
+ private String mode;
+ private BufferedReader reader;
+ private ConsoleParser parser;
+
+ public ConsoleStreamer(String mode, InputStream is)
+ {
+ this.mode = mode;
+ this.reader = new BufferedReader(new InputStreamReader(is));
+ }
+
+ public void setParser(ConsoleParser connector)
+ {
+ this.parser = connector;
+ }
+
+ public void run()
+ {
+ String line;
+ // System.out.printf("ConsoleStreamer/%s initiated%n",mode);
+ try
+ {
+ while ((line = reader.readLine()) != (null))
+ {
+ if (parser != null)
+ {
+ parser.parse(line);
+ }
+ System.out.println("[" + mode + "] " + line);
+ }
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ finally
+ {
+ IO.close(reader);
+ }
+ // System.out.printf("ConsoleStreamer/%s finished%n",mode);
+ }
+ }
+
+ public void setStartTime(long startTime, TimeUnit timeUnit)
+ {
+ this.startTime = startTime;
+ this.timeUnit = timeUnit;
+ }
+} \ No newline at end of file

Back to the top