)` method
+There are also a few session settings that do not have SessionHandler setters, but can be configured with context init parameters:
+
+[[pg-server-session-maxAge]]
+org.eclipse.jetty.servlet.MaxAge::
+This is the maximum number of seconds that the session cookie will be considered to be valid.
+By default, the cookie has no maximum validity time.
+See also xref:pg-server-session-refreshcookie[refreshing the session cookie].
+The value can also be configured by:
+
+* calling the `SessionCookieConfig.setMaxAge(int)` method.
+org.eclipse.jetty.servlet.SessionDomain::
+String, default `null`.
+This is the domain of the session cookie.
+This can also be configured by:
+
+* using the `javax.servlet.SessionCookieConfig.setDomain(String)` method
+* defining the `` element in `web.xml`
+
+
+org.eclipse.jetty.servlet.SessionPath::
+String, default `null`.
+This is used when creating a new session cookie.
+If nothing is configured, the context path is used instead, defaulting to `/`.
+This can also be configured by:
+
+* using the `javax.servlet.SessionCookieConfig.setPath(String)` method
+* defining the `` element in `web.xml`
+
+===== Statistics =====
+
+Some statistics about the sessions for a context can be obtained from the `SessionHandler`, either by calling the methods directly or via `jmx`:
+
+sessionsCreated::
+This is the total number of sessions that have been created for this context since Jetty started.
+
+sessionTimeMax::
+The longest period of time a session was valid in this context before being invalidated.
+
+sessionTimeMean::
+The average period of time a session in this context was valid.
+
+sessionTimeStdDev::
+The standard deviation of the session validity times for this context.
+
+sessionTimeTotal::
+The total time that all sessions in this context have remained valid.
+
+You can reset the statistics counters by either calling the following method directly on the the `SessionHandler`, or using `jmx`:
+
+statsReset::
+Resets the `SessionHandler` statistics counters.
diff --git a/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionidmgr.adoc b/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionidmgr.adoc
index cf8bea3a032..ccd54255335 100644
--- a/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionidmgr.adoc
+++ b/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionidmgr.adoc
@@ -45,7 +45,7 @@ The `DefaultSessionIdManager` uses `SecureRandom` to generate unique session ids
The `SessionHandler` class, which is used by both the `ServletContextHandler` and the `WebAppContext` classes, will instantiate a `DefaultSessionIdManager` on startup if it does not detect one already present for the `Server`.
-Here is an example of explicitly setting up a `DefaultSessionIdManager` in code:
+Here is an example of explicitly setting up a `DefaultSessionIdManager` with a `workerName` of `server3` in code:
[source,java,indent=0]
----
diff --git a/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/sessions.adoc b/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/sessions.adoc
index 2cb88d3c248..af018592ffd 100644
--- a/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/sessions.adoc
+++ b/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/sessions.adoc
@@ -23,5 +23,9 @@ Sessions are a concept within the Servlet API which allow requests to store and
include::session-architecture.adoc[]
include::session-sessionidmgr.adoc[]
+include::session-sessionhandler.adoc[]
include::session-sessioncache.adoc[]
include::session-sessiondatastore.adoc[]
+include::session-sessiondatastore-file.adoc[]
+include::session-sessiondatastore-jdbc.adoc[]
+include::session-cachingsessiondatastore.adoc[]
diff --git a/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java b/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java
index c484c2f82e9..760e6c379f9 100644
--- a/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java
+++ b/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java
@@ -18,15 +18,26 @@
package org.eclipse.jetty.docs.programming.server.session;
+import java.io.File;
+import java.net.InetSocketAddress;
+
+import org.eclipse.jetty.memcached.session.MemcachedSessionDataMapFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.session.CachingSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.DatabaseAdaptor;
+import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.DefaultSessionCacheFactory;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
+import org.eclipse.jetty.server.session.FileSessionDataStore;
+import org.eclipse.jetty.server.session.FileSessionDataStoreFactory;
import org.eclipse.jetty.server.session.HouseKeeper;
import org.eclipse.jetty.server.session.NullSessionCache;
import org.eclipse.jetty.server.session.NullSessionCacheFactory;
import org.eclipse.jetty.server.session.NullSessionDataStore;
import org.eclipse.jetty.server.session.SessionCache;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
public class SessionDocs
@@ -37,7 +48,7 @@ public class SessionDocs
Server server = new Server();
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
//you must set the workerName unless you set the env viable JETTY_WORKER_NAME
- idMgr.setWorkerName("3");
+ idMgr.setWorkerName("server3");
server.setSessionIdManager(idMgr);
//end::default[]
}
@@ -49,7 +60,7 @@ public class SessionDocs
//tag::housekeeper[]
Server server = new Server();
DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
- idMgr.setWorkerName("7");
+ idMgr.setWorkerName("server7");
server.setSessionIdManager(idMgr);
HouseKeeper houseKeeper = new HouseKeeper();
@@ -65,6 +76,38 @@ public class SessionDocs
}
}
+ public void servletContextWithSessionHandler()
+ {
+ //tag:schsession[]
+ Server server = new Server();
+
+ ServletContextHandler context = new ServletContextHandler(server, "/foo", ServletContextHandler.SESSIONS);
+ SessionHandler sessions = context.getSessionHandler();
+ //make idle sessions valid for only 5mins
+ sessions.setMaxInactiveInterval(300);
+ //turn off use of cookies
+ sessions.setUsingCookies(false);
+
+ server.setHandler(context);
+ //end::schsession[]
+ }
+
+ public void webAppWithSessionHandler()
+ {
+ //tag:wacsession[]
+ Server server = new Server();
+
+ WebAppContext context = new WebAppContext();
+ SessionHandler sessions = context.getSessionHandler();
+ //make idle sessions valid for only 5mins
+ sessions.setMaxInactiveInterval(300);
+ //turn off use of cookies
+ sessions.setUsingCookies(false);
+
+ server.setHandler(context);
+ //end::wacsession[]
+ }
+
public void defaultSessionCache()
{
//tag::defaultsessioncache[]
@@ -139,4 +182,110 @@ public class SessionDocs
app2.getSessionHandler().setSessionCache(nullSessionCache);
//end::mixedsessioncache[]
}
+
+ public void fileSessionDataStoreFactory()
+ {
+ //tag::filesessiondatastorefactory[]
+ Server server = new Server();
+
+ //First lets configure a DefaultSessionCacheFactory
+ DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
+ //NEVER_EVICT
+ cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
+ cacheFactory.setFlushOnResponseCommit(true);
+ cacheFactory.setInvalidateOnShutdown(false);
+ cacheFactory.setRemoveUnloadableSessions(true);
+ cacheFactory.setSaveOnCreate(true);
+
+ //Add the factory as a bean to the server, now whenever a
+ //SessionHandler starts it will consult the bean to create a new DefaultSessionCache
+ server.addBean(cacheFactory);
+
+ //Now, lets configure a FileSessionDataStoreFactory
+ FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
+ storeFactory.setStoreDir(new File("/tmp/sessions"));
+ storeFactory.setGracePeriodSec(3600);
+ storeFactory.setSavePeriodSec(0);
+
+ //Add the factory as a bean on the server, now whenever a
+ //SessionHandler starts, it will consult the bean to create a new FileSessionDataStore
+ //for use by the DefaultSessionCache
+ server.addBean(storeFactory);
+ //end::filesessiondatastorefactory[]
+ }
+
+ public void fileSessionDataStore()
+ {
+ //tag::filesessiondatastore[]
+
+ //create a context
+ WebAppContext app1 = new WebAppContext();
+ app1.setContextPath("/app1");
+
+ //First, we create a DefaultSessionCache
+ DefaultSessionCache cache = new DefaultSessionCache(app1.getSessionHandler());
+ cache.setEvictionPolicy(SessionCache.NEVER_EVICT);
+ cache.setFlushOnResponseCommit(true);
+ cache.setInvalidateOnShutdown(false);
+ cache.setRemoveUnloadableSessions(true);
+ cache.setSaveOnCreate(true);
+
+ //Now, we configure a FileSessionDataStore
+ FileSessionDataStore store = new FileSessionDataStore();
+ store.setStoreDir(new File("/tmp/sessions"));
+ store.setGracePeriodSec(3600);
+ store.setSavePeriodSec(0);
+
+ //Tell the cache to use the store
+ cache.setSessionDataStore(store);
+
+ //Tell the contex to use the cache/store combination
+ app1.getSessionHandler().setSessionCache(cache);
+
+ //end::filesessiondatastore[]
+ }
+
+ public void cachingSessionDataStore()
+ {
+ //tag::cachingsds[]
+ Server server = new Server();
+
+ //Make a factory for memcached L2 caches for SessionData
+ MemcachedSessionDataMapFactory mapFactory = new MemcachedSessionDataMapFactory();
+ mapFactory.setExpirySec(0); //items in memcached don't expire
+ mapFactory.setHeartbeats(true);//tell memcached to use heartbeats
+ mapFactory.setAddresses(new InetSocketAddress("localhost", 11211)); //use a local memcached instance
+ mapFactory.setWeights(new int[] {100}); //set the weighting
+
+
+ //Make a FileSessionDataStoreFactory for creating FileSessionDataStores
+ //to persist the session data
+ FileSessionDataStoreFactory storeFactory = new FileSessionDataStoreFactory();
+ storeFactory.setStoreDir(new File("/tmp/sessions"));
+ storeFactory.setGracePeriodSec(3600);
+ storeFactory.setSavePeriodSec(0);
+
+ //Make a factory that plugs the L2 cache into the SessionDataStore
+ CachingSessionDataStoreFactory cachingSessionDataStoreFactory = new CachingSessionDataStoreFactory();
+ cachingSessionDataStoreFactory.setSessionDataMapFactory(mapFactory);
+ cachingSessionDataStoreFactory.setSessionStoreFactory(storeFactory);
+
+ //Register it as a bean so that all SessionHandlers will use it
+ //to make FileSessionDataStores that use memcached as an L2 SessionData cache.
+ server.addBean(cachingSessionDataStoreFactory);
+ //end::cachingsds[]
+ }
+
+ public void jdbcSessionDataStore()
+ {
+ //tag::dbaDatasource[]
+ DatabaseAdaptor datasourceAdaptor = new DatabaseAdaptor();
+ datasourceAdaptor.setDatasourceName("/jdbc/myDS");
+ //end::dbaDatasource[]
+
+ //tag::dbaDriver[]
+ DatabaseAdaptor driverAdaptor = new DatabaseAdaptor();
+ driverAdaptor.setDriverInfo("com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin");
+ //end::dbaDriver[]
+ }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java b/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java
index 518e6ab710a..ed4f50b9942 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java
@@ -29,6 +29,7 @@ import java.util.zip.ZipException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.compression.InflaterPool;
/**
* Decoder for the "gzip" content encoding.
@@ -41,9 +42,10 @@ public class GZIPContentDecoder implements Destroyable
private static final long UINT_MAX = 0xFFFFFFFFL;
private final List _inflateds = new ArrayList<>();
- private final Inflater _inflater = new Inflater(true);
+ private final InflaterPool _inflaterPool;
private final ByteBufferPool _pool;
private final int _bufferSize;
+ private Inflater _inflater;
private State _state;
private int _size;
private long _value;
@@ -62,6 +64,13 @@ public class GZIPContentDecoder implements Destroyable
public GZIPContentDecoder(ByteBufferPool pool, int bufferSize)
{
+ this(null, pool, bufferSize);
+ }
+
+ public GZIPContentDecoder(InflaterPool inflaterPool, ByteBufferPool pool, int bufferSize)
+ {
+ _inflaterPool = inflaterPool;
+ _inflater = (inflaterPool == null) ? new Inflater(true) : inflaterPool.acquire();
_bufferSize = bufferSize;
_pool = pool;
reset();
@@ -207,8 +216,9 @@ public class GZIPContentDecoder implements Destroyable
try
{
- int length = _inflater.inflate(buffer.array(), buffer.arrayOffset(), buffer.capacity());
- buffer.limit(length);
+ int pos = BufferUtil.flipToFill(buffer);
+ _inflater.inflate(buffer);
+ BufferUtil.flipToFlush(buffer, pos);
}
catch (DataFormatException x)
{
@@ -226,23 +236,10 @@ public class GZIPContentDecoder implements Destroyable
{
if (!compressed.hasRemaining())
return;
- if (compressed.hasArray())
- {
- _inflater.setInput(compressed.array(), compressed.arrayOffset() + compressed.position(), compressed.remaining());
- compressed.position(compressed.limit());
- }
- else
- {
- // TODO use the pool
- byte[] input = new byte[compressed.remaining()];
- compressed.get(input);
- _inflater.setInput(input);
- }
+ _inflater.setInput(compressed);
}
else if (_inflater.finished())
{
- int remaining = _inflater.getRemaining();
- compressed.position(compressed.limit() - remaining);
_state = State.CRC;
_size = 0;
_value = 0;
@@ -386,7 +383,6 @@ public class GZIPContentDecoder implements Destroyable
if (_value != (_inflater.getBytesWritten() & UINT_MAX))
throw new ZipException("Invalid input size");
- // TODO ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
reset();
return;
}
@@ -420,7 +416,12 @@ public class GZIPContentDecoder implements Destroyable
@Override
public void destroy()
{
- _inflater.end();
+ if (_inflaterPool == null)
+ _inflater.end();
+ else
+ _inflaterPool.release(_inflater);
+
+ _inflater = null;
}
public boolean isFinished()
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
index 285953681a2..7bf249f2206 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
@@ -639,22 +639,28 @@ public interface HttpFields extends Iterable
public Mutable add(HttpFields fields)
{
+ if (_fields == null)
+ _fields = new HttpField[fields.size() + 4];
+ else if (_size + fields.size() >= _fields.length)
+ _fields = Arrays.copyOf(_fields, _size + fields.size() + 4);
+
+ if (fields.size() == 0)
+ return this;
+
if (fields instanceof Immutable)
{
Immutable b = (Immutable)fields;
- _fields = Arrays.copyOf(b._fields, b._fields.length + 4);
- _size = b._fields.length;
+ System.arraycopy(b._fields, 0, _fields, _size, b._fields.length);
+ _size += b._fields.length;
}
else if (fields instanceof Mutable)
{
Mutable b = (Mutable)fields;
- _fields = Arrays.copyOf(b._fields, b._fields.length);
- _size = b._size;
+ System.arraycopy(b._fields, 0, _fields, _size, b._size);
+ _size += b._size;
}
else
{
- _fields = new HttpField[fields.size() + 4];
- _size = 0;
for (HttpField f : fields)
_fields[_size++] = f;
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
index b80b5d4e879..01f85c1491a 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
@@ -25,14 +25,14 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie;
/**
- *
+ * HTTP and WebSocket Schemes
*/
public enum HttpScheme
{
- HTTP("http"),
- HTTPS("https"),
- WS("ws"),
- WSS("wss");
+ HTTP("http", 80),
+ HTTPS("https", 443),
+ WS("ws", 80),
+ WSS("wss", 443);
public static final Trie CACHE = new ArrayTrie();
@@ -46,11 +46,13 @@ public enum HttpScheme
private final String _string;
private final ByteBuffer _buffer;
+ private final int _defaultPort;
- HttpScheme(String s)
+ HttpScheme(String s, int port)
{
_string = s;
_buffer = BufferUtil.toBuffer(s);
+ _defaultPort = port;
}
public ByteBuffer asByteBuffer()
@@ -60,7 +62,7 @@ public enum HttpScheme
public boolean is(String s)
{
- return s != null && _string.equalsIgnoreCase(s);
+ return _string.equalsIgnoreCase(s);
}
public String asString()
@@ -68,9 +70,31 @@ public enum HttpScheme
return _string;
}
+ public int getDefaultPort()
+ {
+ return _defaultPort;
+ }
+
+ public int normalizePort(int port)
+ {
+ return port == _defaultPort ? 0 : port;
+ }
+
@Override
public String toString()
{
return _string;
}
+
+ public static int getDefaultPort(String scheme)
+ {
+ HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme);
+ return httpScheme == null ? HTTP.getDefaultPort() : httpScheme.getDefaultPort();
+ }
+
+ public static int normalizePort(String scheme, int port)
+ {
+ HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme);
+ return httpScheme == null ? port : httpScheme.normalizePort(port);
+ }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
index f740a2086a0..9a1f017f06a 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
@@ -638,11 +638,12 @@ public interface HttpURI
public Mutable normalize()
{
- if (_port == 80 && HttpScheme.HTTP.is(_scheme))
+ HttpScheme scheme = _scheme == null ? null : HttpScheme.CACHE.get(_scheme);
+ if (scheme != null && _port == scheme.getDefaultPort())
+ {
_port = 0;
- if (_port == 443 && HttpScheme.HTTPS.is(_scheme))
- _port = 0;
- _uri = null;
+ _uri = null;
+ }
return this;
}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
index 4a77a4cdf49..04ebd3905d2 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
@@ -742,6 +742,25 @@ public class HttpFieldsTest
assertThat(fields.size(), is(0));
}
+ @Test
+ public void testAddHttpFields()
+ {
+ HttpFields.Mutable fields = new HttpFields.Mutable(new HttpFields.Mutable());
+ fields.add("One", "1");
+
+ fields = new HttpFields.Mutable(fields);
+
+ fields.add(HttpFields.build().add("two", "2").add("three", "3"));
+ fields.add(HttpFields.build().add("four", "4").add("five", "5").asImmutable());
+
+ assertThat(fields.size(), is(5));
+ assertThat(fields.get("one"), is("1"));
+ assertThat(fields.get("two"), is("2"));
+ assertThat(fields.get("three"), is("3"));
+ assertThat(fields.get("four"), is("4"));
+ assertThat(fields.get("five"), is("5"));
+ }
+
@Test
public void testPutNullName()
{
diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml
index f4a4975cee5..08bf42b5868 100644
--- a/jetty-jspc-maven-plugin/pom.xml
+++ b/jetty-jspc-maven-plugin/pom.xml
@@ -109,7 +109,6 @@
org.apache.ant
ant
- 1.10.8
diff --git a/jetty-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/postbuild.groovy b/jetty-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/postbuild.groovy
index 4e37335b279..97e23b4c98d 100644
--- a/jetty-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/postbuild.groovy
+++ b/jetty-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/postbuild.groovy
@@ -21,7 +21,7 @@ assert buildLog.text.contains( 'Started Server' )
assert buildLog.text.contains( '(1a) >> javax.servlet.ServletContextListener loaded from jar:' )
-assert buildLog.text.contains( '/org/eclipse/jetty/toolchain/jetty-servlet-api/4.0.3/jetty-servlet-api-4.0.3.jar!/javax/servlet/ServletContextListener.class << (1b)' )
+assert buildLog.text.contains( '/org/eclipse/jetty/toolchain/jetty-servlet-api/4.0.4/jetty-servlet-api-4.0.4.jar!/javax/servlet/ServletContextListener.class << (1b)' )
assert buildLog.text.contains( '(2a) >> mca.common.CommonService loaded from file:' )
assert buildLog.text.contains( 'common/target/classes/mca/common/CommonService.class << (2b)' )
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java
index d588cefd1bf..822de9a144f 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WebAppPropertyConverter.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import java.util.stream.Collectors;
import org.eclipse.jetty.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
@@ -337,17 +338,8 @@ public class WebAppPropertyConverter
* @param resources the resources to convert
* @return csv string of resource filenames
*/
- private static String toCSV(Resource[] resources)
+ private static String toCSV(List resources)
{
- StringBuilder rb = new StringBuilder();
-
- for (Resource r : resources)
- {
- if (rb.length() > 0)
- rb.append(",");
- rb.append(r.toString());
- }
-
- return rb.toString();
+ return resources.stream().map(Object::toString).collect(Collectors.joining(","));
}
}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
index f4b78fc8f3b..846f1098998 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
@@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
-import org.objectweb.asm.Opcodes;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
@@ -145,7 +144,7 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
}
});
boolean hasDotPath = false;
- StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false);
+ StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, StringUtil.DEFAULT_DELIMS, false);
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
index 89fcfea0d5a..abb1f1e2e7d 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
@@ -233,7 +233,7 @@ public class ServerInstanceWrapper
Thread.currentThread().setContextClassLoader(libExtClassLoader);
String jettyConfigurationUrls = (String)props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS);
- List jettyConfigurations = jettyConfigurationUrls != null ? Util.fileNamesAsURLs(jettyConfigurationUrls, Util.DEFAULT_DELIMS) : null;
+ List jettyConfigurations = jettyConfigurationUrls != null ? Util.fileNamesAsURLs(jettyConfigurationUrls, StringUtil.DEFAULT_DELIMS) : null;
_server = configure(server, jettyConfigurations, props);
@@ -418,7 +418,7 @@ public class ServerInstanceWrapper
List libURLs = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(sharedURLs, ",;", false);
+ StringTokenizer tokenizer = new StringTokenizer(sharedURLs, StringUtil.DEFAULT_DELIMS, false);
while (tokenizer.hasMoreTokens())
{
String tok = tokenizer.nextToken();
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
index b55184c247b..636ccc5fd6a 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
@@ -20,15 +20,12 @@ package org.eclipse.jetty.osgi.boot.internal.webapp;
import java.io.File;
import java.io.IOException;
-import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.StringTokenizer;
import java.util.jar.JarFile;
import javax.servlet.http.HttpServlet;
@@ -201,22 +198,16 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
@Override
public void addClassPath(String classPath) throws IOException
{
-
- StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
- while (tokenizer.hasMoreTokens())
+ for (Resource resource : Resource.fromList(classPath, false, (path) -> getContext().newResource(path)))
{
- String path = tokenizer.nextToken();
- Resource resource = getContext().newResource(path);
-
- // Resolve file path if possible
File file = resource.getFile();
if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
{
- super.addClassPath(path);
+ super.addClassPath(resource);
}
else
{
- LOG.info("Did not add {} to the classloader of the webapp {}", path, getContext());
+ LOG.info("Did not add {} to the classloader of the webapp {}", resource, getContext());
}
}
}
@@ -272,37 +263,4 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
}
return true;
}
-
- private static Field _contextField;
-
- /**
- * In the case of the generation of a webapp via a jetty context file we
- * need a proper classloader to setup the app before we have the
- * WebappContext So we place a fake one there to start with. We replace it
- * with the actual webapp context with this method. We also apply the
- * extraclasspath there at the same time.
- *
- * @param webappContext the web app context
- */
- public void setWebappContext(WebAppContext webappContext)
- {
- try
- {
- if (_contextField == null)
- {
- _contextField = WebAppClassLoader.class.getDeclaredField("_context");
- _contextField.setAccessible(true);
- }
- _contextField.set(this, webappContext);
- if (webappContext.getExtraClasspath() != null)
- {
- addClassPath(webappContext.getExtraClasspath());
- }
- }
- catch (Throwable t)
- {
- // humf that will hurt if it does not work.
- LOG.warn("Unable to set webappcontext", t);
- }
- }
}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java
index b5e5b370945..997784dc484 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java
@@ -35,8 +35,6 @@ import org.osgi.framework.InvalidSyntaxException;
*/
public class Util
{
- public static final String DEFAULT_DELIMS = ",;";
-
/**
* Create an osgi filter for the given classname and server name.
*
@@ -101,7 +99,7 @@ public class Util
public static List fileNamesAsURLs(String val, String delims)
throws Exception
{
- String separators = DEFAULT_DELIMS;
+ String separators = StringUtil.DEFAULT_DELIMS;
if (delims == null)
delims = separators;
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java
index 109fc0ebae1..2ab4c033c68 100644
--- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java
@@ -51,6 +51,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
@@ -212,6 +213,9 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
if (context.getServletHandler().getListeners() != null)
for (ListenerHolder e : context.getServletHandler().getListeners())
{
+ if (e.getSource() == Source.EMBEDDED)
+ continue;
+
out.openTag("listener", origin(md, e.getClassName() + ".listener"))
.tag("listener-class", e.getClassName())
.closeTag();
@@ -223,14 +227,20 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
{
for (FilterHolder holder : servlets.getFilters())
{
+ if (holder.getSource() == Source.EMBEDDED)
+ continue;
outholder(out, md, holder);
}
}
if (servlets.getFilterMappings() != null)
{
- for (FilterMapping mapping : servlets.getFilterMappings())
+ for (FilterMapping mapping :servlets.getFilterMappings())
{
+ FilterHolder f = servlets.getFilter(mapping.getFilterName());
+ if (f != null && f.getSource() == Source.EMBEDDED)
+ continue;
+
out.openTag("filter-mapping");
out.tag("filter-name", mapping.getFilterName());
if (mapping.getPathSpecs() != null)
@@ -265,6 +275,8 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
{
for (ServletHolder holder : servlets.getServlets())
{
+ if (holder.getSource() == Source.EMBEDDED)
+ continue;
outholder(out, md, holder);
}
}
@@ -273,6 +285,10 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
{
for (ServletMapping mapping : servlets.getServletMappings())
{
+ ServletHolder sh = servlets.getServlet(mapping.getServletName());
+ if (sh != null && sh.getSource() == Source.EMBEDDED)
+ continue;
+
out.openTag("servlet-mapping", origin(md, mapping.getServletName() + ".servlet.mappings"));
out.tag("servlet-name", mapping.getServletName());
if (mapping.getPathSpecs() != null)
diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/FooFilter.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/FooFilter.java
new file mode 100644
index 00000000000..376cd8c5490
--- /dev/null
+++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/FooFilter.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class FooFilter implements Filter
+{
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+}
diff --git a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java
index fafecd9d802..800c05480b8 100644
--- a/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java
+++ b/jetty-quickstart/src/test/java/org/eclipse/jetty/quickstart/TestQuickStart.java
@@ -19,19 +19,23 @@
package org.eclipse.jetty.quickstart;
import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
+import java.util.Arrays;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -42,17 +46,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class TestQuickStart
{
- File testDir;
- File webInf;
Server server;
@BeforeEach
public void setUp()
{
- testDir = MavenTestingUtils.getTargetTestingDir("foo");
- FS.ensureEmpty(testDir);
- webInf = new File(testDir, "WEB-INF");
- FS.ensureDirExists(webInf);
server = new Server();
}
@@ -65,6 +63,11 @@ public class TestQuickStart
@Test
public void testProgrammaticOverrideOfDefaultServletMapping() throws Exception
{
+ File testDir = MavenTestingUtils.getTargetTestingDir("pgoverride");
+ FS.ensureEmpty(testDir);
+ File webInf = new File(testDir, "WEB-INF");
+ FS.ensureDirExists(webInf);
+
File quickstartXml = new File(webInf, "quickstart-web.xml");
assertFalse(quickstartXml.exists());
@@ -87,27 +90,36 @@ public class TestQuickStart
assertTrue(quickstartXml.exists());
- //now run the webapp again purely from the generated quickstart
+ //now run the webapp again
WebAppContext webapp = new WebAppContext();
webapp.setResourceBase(testDir.getAbsolutePath());
webapp.addConfiguration(new QuickStartConfiguration());
+ webapp.getServerClassMatcher().exclude("org.eclipse.jetty.quickstart.");
webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
- webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
+ //add in the servlet
+ webapp.getServletHandler().addServlet(fooHolder);
+ //add in the listener
+ webapp.getServletHandler().addListener(lholder);
+
server.setHandler(webapp);
server.setDryRun(false);
server.start();
- server.dumpStdErr();
//verify that FooServlet is now mapped to / and not the DefaultServlet
ServletHolder sh = webapp.getServletHandler().getMappedServlet("/").getServletHolder();
assertNotNull(sh);
- assertEquals("foo", sh.getName());
+ assertThat(sh.getClassName(), Matchers.equalTo("org.eclipse.jetty.quickstart.FooServlet"));
}
@Test
public void testDefaultContextPath() throws Exception
{
+ File testDir = MavenTestingUtils.getTargetTestingDir("dfltcp");
+ FS.ensureEmpty(testDir);
+ File webInf = new File(testDir, "WEB-INF");
+ FS.ensureDirExists(webInf);
+
File quickstartXml = new File(webInf, "quickstart-web.xml");
assertFalse(quickstartXml.exists());
@@ -132,7 +144,7 @@ public class TestQuickStart
webapp.addConfiguration(new QuickStartConfiguration());
quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setResourceBase(testDir.getAbsolutePath());
- webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
+ webapp.getServerClassMatcher().exclude("org.eclipse.jetty.quickstart.");
server.setHandler(webapp);
server.setDryRun(false);
@@ -146,6 +158,11 @@ public class TestQuickStart
@Test
public void testDefaultRequestAndResponseEncodings() throws Exception
{
+ File testDir = MavenTestingUtils.getTargetTestingDir("dfltenc");
+ FS.ensureEmpty(testDir);
+ File webInf = new File(testDir, "WEB-INF");
+ FS.ensureDirExists(webInf);
+
File quickstartXml = new File(webInf, "quickstart-web.xml");
assertFalse(quickstartXml.exists());
@@ -168,7 +185,7 @@ public class TestQuickStart
webapp.addConfiguration(new QuickStartConfiguration());
quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setResourceBase(testDir.getAbsolutePath());
- webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
+ webapp.getServerClassMatcher().exclude("org.eclipse.jetty.quickstart.");
server.setHandler(webapp);
server.setDryRun(false);
@@ -181,6 +198,11 @@ public class TestQuickStart
@Test
public void testListenersNotCalledInPreConfigure() throws Exception
{
+ File testDir = MavenTestingUtils.getTargetTestingDir("listeners");
+ FS.ensureEmpty(testDir);
+ File webInf = new File(testDir, "WEB-INF");
+ FS.ensureDirExists(webInf);
+
File quickstartXml = new File(webInf, "quickstart-web.xml");
assertFalse(quickstartXml.exists());
@@ -203,4 +225,69 @@ public class TestQuickStart
assertTrue(quickstartXml.exists());
assertEquals(0, FooContextListener.___initialized);
}
+
+ @Test
+ public void testDuplicateGenerationFromContextXml() throws Exception
+ {
+ File testDir = MavenTestingUtils.getTargetTestingDir("dups");
+ FS.ensureEmpty(testDir);
+ File webInf = new File(testDir, "WEB-INF");
+ FS.ensureDirExists(webInf);
+
+ File quickstartXml = new File(webInf, "quickstart-web.xml");
+ assertFalse(quickstartXml.exists());
+
+ //no servlets, filters or listeners defined in web.xml
+ WebAppContext quickstart = new WebAppContext();
+ quickstart.addConfiguration(new QuickStartConfiguration());
+ quickstart.setWar(testDir.toURI().toURL().toExternalForm());
+ quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE);
+ quickstart.setDescriptor(MavenTestingUtils.getTestResourceFile("web.xml").getAbsolutePath());
+
+ //apply the context xml file
+ XmlConfiguration xmlConfig = new XmlConfiguration(Resource.newResource(MavenTestingUtils.getTestResourceFile("context.xml")));
+ xmlConfig.configure(quickstart);
+
+ //generate the quickstart
+ server.setHandler(quickstart);
+ server.setDryRun(true);
+ server.start();
+
+ assertTrue(quickstartXml.exists());
+ assertTrue(server.isStopped());
+
+ //Make a new webappcontext to mimic starting the server over again with
+ //a freshly applied context xml
+ quickstart = new WebAppContext();
+ //need visibility of FooServlet, FooFilter, FooContextListener when we quickstart
+ quickstart.getServerClassMatcher().exclude("org.eclipse.jetty.quickstart.");
+ quickstart.addConfiguration(new QuickStartConfiguration());
+ quickstart.setWar(testDir.toURI().toURL().toExternalForm());
+ quickstart.setDescriptor(MavenTestingUtils.getTestResourceFile("web.xml").getAbsolutePath());
+ quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.AUTO);
+ server.setHandler(quickstart);
+
+ //apply the context xml file like a restart would
+ xmlConfig.configure(quickstart);
+ server.setDryRun(false);
+
+ //restart the server
+ server.start();
+
+ //test that we only get 1 FoOServlet, FooFilter and FooContextListener
+ ServletHolder[] servlets = quickstart.getServletHandler().getServlets();
+ assertNotNull(servlets);
+ assertEquals(1,
+ Arrays.stream(servlets).filter(s -> "org.eclipse.jetty.quickstart.FooServlet".equals(s.getClassName())).count());
+
+ FilterHolder[] filters = quickstart.getServletHandler().getFilters();
+ assertNotNull(filters);
+ assertEquals(1,
+ Arrays.stream(filters).filter(f -> "org.eclipse.jetty.quickstart.FooFilter".equals(f.getClassName())).count());
+
+ ListenerHolder[] listeners = quickstart.getServletHandler().getListeners();
+ assertNotNull(listeners);
+ assertEquals(1,
+ Arrays.stream(listeners).filter(l -> "org.eclipse.jetty.quickstart.FooContextListener".equals(l.getClassName())).count());
+ }
}
diff --git a/jetty-quickstart/src/test/resources/context.xml b/jetty-quickstart/src/test/resources/context.xml
new file mode 100644
index 00000000000..1ac22a00e2c
--- /dev/null
+++ b/jetty-quickstart/src/test/resources/context.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ /test
+
+
+
+
+
+ FooServlet
+ org.eclipse.jetty.quickstart.FooServlet
+
+
+ /outer/*
+
+
+
+
+
+ org.eclipse.jetty.quickstart.FooFilter
+ OuterFilter
+
+
+ /outer/*
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/jetty-gzip.xml b/jetty-server/src/main/config/etc/jetty-gzip.xml
index 24ccbbb4b8c..16f60c92354 100644
--- a/jetty-server/src/main/config/etc/jetty-gzip.xml
+++ b/jetty-server/src/main/config/etc/jetty-gzip.xml
@@ -16,10 +16,16 @@
+
+
+
+
+
+
false
@@ -690,7 +691,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.5
+ 0.8.6
com.agilejava.docbkx
@@ -734,7 +735,7 @@
org.asciidoctor
asciidoctor-maven-plugin
- 1.5.6
+ 2.1.0
org.codehaus.mojo
@@ -1152,6 +1153,16 @@
grpc-core
1.0.1