scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(StringUtil.csvSplit(scopeString));
+ if (!scopes.contains("openid"))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope");
+ return;
+ }
+
+ if (!"code".equals(req.getParameter("response_type")))
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code");
+ return;
+ }
+
+ String state = req.getParameter("state");
+ if (state == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param");
+ return;
+ }
+
+ String authCode = UUID.randomUUID().toString().replace("-", "");
+ User user = new User(123456789, "FirstName", "LastName");
+ issuedAuthCodes.put(authCode, user);
+
+ final Request baseRequest = Request.getBaseRequest(req);
+ final Response baseResponse = baseRequest.getResponse();
+ redirectUri += "?code=" + authCode + "&state=" + state;
+ int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ?
+ HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ baseResponse.sendRedirect(redirectCode, resp.encodeRedirectURL(redirectUri));
+ }
+ }
+
+ public class OpenIdTokenEndpoint extends HttpServlet
+ {
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ String code = req.getParameter("code");
+
+ if (!clientId.equals(req.getParameter("client_id")) ||
+ !clientSecret.equals(req.getParameter("client_secret")) ||
+ !redirectUris.contains(req.getParameter("redirect_uri")) ||
+ !"authorization_code".equals(req.getParameter("grant_type")) ||
+ code == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request");
+ return;
+ }
+
+ User user = issuedAuthCodes.remove(code);
+ if (user == null)
+ {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code");
+ return;
+ }
+
+ String jwtHeader = "{\"INFO\": \"this is not used or checked in our implementation\"}";
+ String jwtBody = user.getIdToken();
+ String jwtSignature = "we do not validate signature as we use the authorization code flow";
+
+ Base64.Encoder encoder = Base64.getEncoder();
+ String jwt = encoder.encodeToString(jwtHeader.getBytes()) + "." +
+ encoder.encodeToString(jwtBody.getBytes()) + "." +
+ encoder.encodeToString(jwtSignature.getBytes());
+
+ String accessToken = "ABCDEFG";
+ long expiry = System.currentTimeMillis() + Duration.ofMinutes(10).toMillis();
+ String response = "{" +
+ "\"access_token\": \"" + accessToken + "\"," +
+ "\"id_token\": \"" + jwt + "\"," +
+ "\"expires_in\": " + expiry + "," +
+ "\"token_type\": \"Bearer\"" +
+ "}";
+
+ resp.setContentType("text/plain");
+ resp.getWriter().print(response);
+ }
+ }
+
+ public class OpenIdConfigServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ String discoveryDocument = "{" +
+ "\"issuer\": \"" + provider + "\"," +
+ "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," +
+ "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," +
+ "}";
+
+ resp.getWriter().write(discoveryDocument);
+ }
+ }
+
+ public class User
+ {
+ private long subject;
+ private String firstName;
+ private String lastName;
+
+ public User(String firstName, String lastName)
+ {
+ this(new Random().nextLong(), firstName, lastName);
+ }
+
+ public User(long subject, String firstName, String lastName)
+ {
+ this.subject = subject;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getFirstName()
+ {
+ return firstName;
+ }
+
+ public String getLastName()
+ {
+ return lastName;
+ }
+
+ public String getIdToken()
+ {
+ return "{" +
+ "\"iss\": \"" + provider + "\"," +
+ "\"sub\": \"" + subject + "\"," +
+ "\"aud\": \"" + clientId + "\"," +
+ "\"exp\": " + System.currentTimeMillis() + Duration.ofMinutes(1).toMillis() + "," +
+ "\"name\": \"" + firstName + " " + lastName + "\"," +
+ "\"email\": \"" + firstName + "@fake-email.com" + "\"" +
+ "}";
+ }
+ }
+}
diff --git a/jetty-openid/src/test/resources/jetty-logging.properties b/jetty-openid/src/test/resources/jetty-logging.properties
new file mode 100755
index 00000000000..c73ac07f8ac
--- /dev/null
+++ b/jetty-openid/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+# org.eclipse.jetty.LEVEL=DEBUG
+# org.eclipse.jetty.security.openid.LEVEL=DEBUG
\ No newline at end of file
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
index c64d64fb571..a9dbb178f84 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
@@ -78,6 +78,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
@@ -88,6 +89,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+@Disabled("See issue #3974")
public class AsyncMiddleManServletTest
{
private static final Logger LOG = Log.getLogger(AsyncMiddleManServletTest.class);
diff --git a/jetty-quickstart/src/main/config/etc/example-quickstart.xml b/jetty-quickstart/src/main/config/etc/example-quickstart.xml
deleted file mode 100644
index c042d89d724..00000000000
--- a/jetty-quickstart/src/main/config/etc/example-quickstart.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
- true
- /
- /application.war
-
-
diff --git a/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml b/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml
new file mode 100644
index 00000000000..84d030260b0
--- /dev/null
+++ b/jetty-quickstart/src/main/config/etc/jetty-quickstart.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ [
+
+
+
+
+
+
+
+ ]
+
+ [
+
+
+
+
+ /etc/quickstart-webapp.xml
+
+
+
+
+ ]
+
diff --git a/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml b/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml
new file mode 100644
index 00000000000..b0f07545e95
--- /dev/null
+++ b/jetty-quickstart/src/main/config/modules/jetty-quickstart.d/quickstart-webapp.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ org.eclipse.jetty.quickstart.origin
+
+
+
+
+ org.eclipse.jetty.quickstart.xml
+
+
+
+
+ org.eclipse.jetty.quickstart.mode
+
+
+
+
+
+
+
+ true
+ false
+ false
+
+
diff --git a/jetty-quickstart/src/main/config/modules/quickstart.mod b/jetty-quickstart/src/main/config/modules/quickstart.mod
index 102801714b6..c531ea648d0 100644
--- a/jetty-quickstart/src/main/config/modules/quickstart.mod
+++ b/jetty-quickstart/src/main/config/modules/quickstart.mod
@@ -6,8 +6,21 @@ deployment of preconfigured webapplications.
[depend]
server
-plus
-annotations
+deploy
[lib]
lib/jetty-quickstart-${jetty.version}.jar
+
+[xml]
+etc/jetty-quickstart.xml
+
+[files]
+basehome:modules/jetty-quickstart.d/quickstart-webapp.xml|etc/quickstart-webapp.xml
+
+
+[ini-template]
+
+# Modes are AUTO, GENERATE, QUICKSTART
+# jetty.quickstart.mode=AUTO
+# jetty.quickstart.origin=origin
+# jetty.quickstart.xml=
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
index 627f676588a..92814695c33 100644
--- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java
@@ -20,11 +20,15 @@ package org.eclipse.jetty.quickstart;
import java.util.Locale;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
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.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
public class PreconfigureQuickStartWar
@@ -98,7 +102,13 @@ public class PreconfigureQuickStartWar
final Server server = new Server();
- QuickStartWebApp webapp = new QuickStartWebApp();
+ WebAppContext webapp = new WebAppContext();
+ webapp.addConfiguration(new QuickStartConfiguration(),
+ new EnvConfiguration(),
+ new PlusConfiguration(),
+ new AnnotationConfiguration());
+ webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE);
+ webapp.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "");
if (xml != null)
{
@@ -108,10 +118,21 @@ public class PreconfigureQuickStartWar
xmlConfiguration.configure(webapp);
}
webapp.setResourceBase(dir.getFile().getAbsolutePath());
- webapp.setMode(QuickStartConfiguration.Mode.GENERATE);
server.setHandler(webapp);
- server.start();
- server.stop();
+ try
+ {
+ server.setDryRun(true);
+ server.start();
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ if (!server.isStopped())
+ server.stop();
+ }
}
private static void error(String message)
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
index 2462ad4e326..bb1a9c3d345 100644
--- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.quickstart;
+import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@@ -26,6 +27,8 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.annotations.AnnotationDecorator;
import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@@ -39,17 +42,16 @@ import org.eclipse.jetty.webapp.WebXmlConfiguration;
/**
* QuickStartConfiguration
*
- * Re-inflate a deployable webapp from a saved effective-web.xml
- * which combines all pre-parsed web xml descriptors and annotations.
+ * Prepare for quickstart generation, or usage.
*/
public class QuickStartConfiguration extends AbstractConfiguration
{
private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class);
public static final Set> __replacedConfigurations = new HashSet<>();
- public static final String ORIGIN_ATTRIBUTE = "org.eclipse.jetty.quickstart.ORIGIN_ATTRIBUTE";
- public static final String GENERATE_ORIGIN = "org.eclipse.jetty.quickstart.GENERATE_ORIGIN";
- public static final String QUICKSTART_WEB_XML = "org.eclipse.jetty.quickstart.QUICKSTART_WEB_XML";
+ public static final String ORIGIN_ATTRIBUTE = "org.eclipse.jetty.quickstart.origin";
+ public static final String QUICKSTART_WEB_XML = "org.eclipse.jetty.quickstart.xml";
+ public static final String MODE = "org.eclipse.jetty.quickstart.mode";
static
{
@@ -59,18 +61,25 @@ public class QuickStartConfiguration extends AbstractConfiguration
__replacedConfigurations.add(org.eclipse.jetty.annotations.AnnotationConfiguration.class);
}
- ;
+ /** Configure the server for the quickstart mode.
+ * In practise this means calling server.setDryRun(true)
for GENERATE mode
+ * @see Server#setDryRun(boolean)
+ * @param server The server to configure
+ * @param mode The quickstart mode
+ */
+ public static void configureMode(Server server, String mode)
+ {
+ if (mode != null && Mode.valueOf(mode) == Mode.GENERATE)
+ server.setDryRun(true);
+ }
public enum Mode
{
- DISABLED, // No Quick start
GENERATE, // Generate quickstart-web.xml and then stop
AUTO, // use or generate depending on the existance of quickstart-web.xml
QUICKSTART // Use quickstart-web.xml
}
- ;
-
private Mode _mode = Mode.AUTO;
private boolean _quickStart;
@@ -81,19 +90,6 @@ public class QuickStartConfiguration extends AbstractConfiguration
addDependents(WebXmlConfiguration.class);
}
- public void setMode(Mode mode)
- {
- _mode = mode;
- }
-
- public Mode getMode()
- {
- return _mode;
- }
-
- /**
- * @see org.eclipse.jetty.webapp.AbstractConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
- */
@Override
public void preConfigure(WebAppContext context) throws Exception
{
@@ -106,39 +102,46 @@ public class QuickStartConfiguration extends AbstractConfiguration
Resource quickStartWebXml = getQuickStartWebXml(context);
LOG.debug("quickStartWebXml={} exists={}", quickStartWebXml, quickStartWebXml.exists());
+ //Get the mode
+ Mode mode = (Mode)context.getAttribute(MODE);
+ if (mode != null)
+ _mode = mode;
+
_quickStart = false;
+
switch (_mode)
{
- case DISABLED:
- super.preConfigure(context);
- break;
-
case GENERATE:
{
+ if (quickStartWebXml.exists())
+ LOG.info("Regenerating {}", quickStartWebXml);
+ else
+ LOG.info("Generating {}", quickStartWebXml);
+
super.preConfigure(context);
+ //generate the quickstart file then abort
QuickStartGeneratorConfiguration generator = new QuickStartGeneratorConfiguration(true);
configure(generator, context);
context.addConfiguration(generator);
break;
}
-
case AUTO:
{
if (quickStartWebXml.exists())
- quickStart(context, quickStartWebXml);
+ {
+ quickStart(context);
+ }
else
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("No quickstart xml file, starting webapp {} normally", context);
super.preConfigure(context);
- QuickStartGeneratorConfiguration generator = new QuickStartGeneratorConfiguration(false);
- configure(generator, context);
- context.addConfiguration(generator);
}
break;
}
-
case QUICKSTART:
if (quickStartWebXml.exists())
- quickStart(context, quickStartWebXml);
+ quickStart(context);
else
throw new IllegalStateException("No " + quickStartWebXml);
break;
@@ -151,27 +154,20 @@ public class QuickStartConfiguration extends AbstractConfiguration
protected void configure(QuickStartGeneratorConfiguration generator, WebAppContext context) throws IOException
{
Object attr;
- attr = context.getAttribute(GENERATE_ORIGIN);
- if (attr != null)
- generator.setGenerateOrigin(Boolean.valueOf(attr.toString()));
attr = context.getAttribute(ORIGIN_ATTRIBUTE);
if (attr != null)
generator.setOriginAttribute(attr.toString());
- attr = context.getAttribute(QUICKSTART_WEB_XML);
- if (attr instanceof Resource)
- generator.setQuickStartWebXml((Resource)attr);
- else if (attr != null)
- generator.setQuickStartWebXml(Resource.newResource(attr.toString()));
+
+ generator.setQuickStartWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML));
}
- /**
- * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
- */
@Override
public void configure(WebAppContext context) throws Exception
{
if (!_quickStart)
+ {
super.configure(context);
+ }
else
{
//add the processor to handle normal web.xml content
@@ -195,14 +191,27 @@ public class QuickStartConfiguration extends AbstractConfiguration
}
}
- protected void quickStart(WebAppContext context, Resource quickStartWebXml)
+ @Override
+ public void postConfigure(WebAppContext context) throws Exception
+ {
+ super.postConfigure(context);
+ ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER);
+ if (starter != null)
+ {
+ context.removeBean(starter);
+ context.removeAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER);
+ }
+ }
+
+ protected void quickStart(WebAppContext context)
throws Exception
{
+ LOG.info("Quickstarting {}", context);
_quickStart = true;
- context.setConfigurations(context.getWebAppConfigurations().stream()
+ context.setConfigurations(context.getConfigurations().stream()
.filter(c -> !__replacedConfigurations.contains(c.replaces()) && !__replacedConfigurations.contains(c.getClass()))
.collect(Collectors.toList()).toArray(new Configuration[]{}));
- context.getMetaData().setWebXml(quickStartWebXml);
+ context.getMetaData().setWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion());
}
@@ -216,12 +225,38 @@ public class QuickStartConfiguration extends AbstractConfiguration
*/
public Resource getQuickStartWebXml(WebAppContext context) throws Exception
{
+ Object attr = context.getAttribute(QUICKSTART_WEB_XML);
+ if (attr instanceof Resource)
+ return (Resource)attr;
+
Resource webInf = context.getWebInf();
if (webInf == null || !webInf.exists())
- throw new IllegalStateException("No WEB-INF");
- LOG.debug("webinf={}", webInf);
+ {
+ File tmp = new File(context.getBaseResource().getFile(), "WEB-INF");
+ tmp.mkdirs();
+ webInf = context.getWebInf();
+ }
- Resource quickStartWebXml = webInf.addPath("quickstart-web.xml");
- return quickStartWebXml;
+ Resource qstart;
+ if (attr == null || StringUtil.isBlank(attr.toString()))
+ {
+ qstart = webInf.addPath("quickstart-web.xml");
+ }
+ else
+ {
+ try
+ {
+ // Try a relative resolution
+ qstart = Resource.newResource(webInf.getFile().toPath().resolve(attr.toString()));
+ }
+ catch (Throwable th)
+ {
+ // try as a resource
+ qstart = (Resource.newResource(attr.toString()));
+ }
+ context.setAttribute(QUICKSTART_WEB_XML, qstart);
+ }
+ context.setAttribute(QUICKSTART_WEB_XML, qstart);
+ return qstart;
}
}
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 20cfab4535b..5588f4b907b 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
@@ -52,6 +52,7 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@@ -61,10 +62,11 @@ 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.webapp.WebInfConfiguration;
import org.eclipse.jetty.xml.XmlAppendable;
/**
- * QuickStartDescriptorGenerator
+ * QuickStartGeneratorConfiguration
*
* Generate an effective web.xml from a WebAppContext, including all components
* from web.xml, web-fragment.xmls annotations etc.
@@ -81,10 +83,9 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
protected final boolean _abort;
protected String _originAttribute;
- protected boolean _generateOrigin;
protected int _count;
protected Resource _quickStartWebXml;
-
+
public QuickStartGeneratorConfiguration()
{
this(false);
@@ -92,7 +93,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
public QuickStartGeneratorConfiguration(boolean abort)
{
- super(true);
+ super(false);
_count = 0;
_abort = abort;
}
@@ -116,22 +117,6 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
return _originAttribute;
}
- /**
- * @return the generateOrigin
- */
- public boolean isGenerateOrigin()
- {
- return _generateOrigin;
- }
-
- /**
- * @param generateOrigin the generateOrigin to set
- */
- public void setGenerateOrigin(boolean generateOrigin)
- {
- _generateOrigin = generateOrigin;
- }
-
public Resource getQuickStartWebXml()
{
return _quickStartWebXml;
@@ -163,8 +148,6 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
if (context.getBaseResource() == null)
throw new IllegalArgumentException("No base resource for " + this);
- LOG.info("Quickstart generating");
-
MetaData md = context.getMetaData();
Map webappAttr = new HashMap<>();
@@ -195,13 +178,13 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
//the META-INF/resources discovered
addContextParamFromAttribute(context, out, MetaInfConfiguration.METAINF_RESOURCES, normalizer);
- // the default-context-path, if presernt
+ // the default-context-path, if present
String defaultContextPath = (String)context.getAttribute("default-context-path");
if (defaultContextPath != null)
out.tag("default-context-path", defaultContextPath);
//add the name of the origin attribute, if it is being used
- if (_generateOrigin)
+ if (StringUtil.isNotBlank(_originAttribute))
{
out.openTag("context-param")
.tag("param-name", ORIGIN)
@@ -766,7 +749,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
*/
public Map origin(MetaData md, String name)
{
- if (!(_generateOrigin || LOG.isDebugEnabled()))
+ if (StringUtil.isBlank(_originAttribute))
return Collections.emptyMap();
if (name == null)
return Collections.emptyMap();
@@ -792,13 +775,19 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
{
MetaData metadata = context.getMetaData();
metadata.resolve(context);
-
- Resource quickStartWebXml = _quickStartWebXml;
- if (_quickStartWebXml == null)
- quickStartWebXml = context.getWebInf().addPath("/quickstart-web.xml");
- try (FileOutputStream fos = new FileOutputStream(quickStartWebXml.getFile(), false))
+ try (FileOutputStream fos = new FileOutputStream(_quickStartWebXml.getFile(), false))
{
generateQuickStartWebXml(context, fos);
+ LOG.info("Generated {}", _quickStartWebXml);
+ if (context.getAttribute(WebInfConfiguration.TEMPORARY_RESOURCE_BASE) != null && !context.isPersistTempDirectory())
+ LOG.warn("Generated to non persistent location: " + _quickStartWebXml);
}
}
+
+ @Override
+ public void deconfigure(WebAppContext context) throws Exception
+ {
+ super.deconfigure(context);
+ }
+
}
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
deleted file mode 100644
index 0cf95f8f1b5..00000000000
--- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2019 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.plus.webapp.EnvConfiguration;
-import org.eclipse.jetty.plus.webapp.PlusConfiguration;
-import org.eclipse.jetty.quickstart.QuickStartConfiguration.Mode;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-/**
- * QuickStartWar
- */
-public class QuickStartWebApp extends WebAppContext
-{
- private final QuickStartConfiguration _quickStartConfiguration;
-
- private String _originAttribute;
- private boolean _generateOrigin;
-
- public QuickStartWebApp()
- {
- super();
- addConfiguration(
- _quickStartConfiguration = new QuickStartConfiguration(),
- new EnvConfiguration(),
- new PlusConfiguration(),
- new AnnotationConfiguration());
- setExtractWAR(true);
- setCopyWebDir(false);
- setCopyWebInf(false);
- }
-
- public void setOriginAttribute(String name)
- {
- setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, name);
- }
-
- /**
- * @return the originAttribute
- */
- public String getOriginAttribute()
- {
- Object attr = getAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE);
- return attr == null ? null : attr.toString();
- }
-
- /**
- * @param generateOrigin the generateOrigin to set
- */
- public void setGenerateOrigin(boolean generateOrigin)
- {
- setAttribute(QuickStartConfiguration.GENERATE_ORIGIN, generateOrigin);
- }
-
- /**
- * @return the generateOrigin
- */
- public boolean isGenerateOrigin()
- {
- Object attr = getAttribute(QuickStartConfiguration.GENERATE_ORIGIN);
- return attr == null ? false : Boolean.valueOf(attr.toString());
- }
-
- public Mode getMode()
- {
- return _quickStartConfiguration.getMode();
- }
-
- public void setMode(Mode mode)
- {
- _quickStartConfiguration.setMode(mode);
- }
-}
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 6e7b3ed3834..82e0c376f8f 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
@@ -27,6 +27,7 @@ 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.webapp.WebAppContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -61,10 +62,11 @@ public class TestQuickStart
Server server = new Server();
//generate a quickstart-web.xml
- QuickStartWebApp quickstart = new QuickStartWebApp();
+ WebAppContext quickstart = new WebAppContext();
+ quickstart.addConfiguration(new QuickStartConfiguration());
+ quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE);
+ quickstart.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "origin");
quickstart.setResourceBase(testDir.getAbsolutePath());
- quickstart.setMode(QuickStartConfiguration.Mode.GENERATE);
- quickstart.setGenerateOrigin(true);
ServletHolder fooHolder = new ServletHolder();
fooHolder.setServlet(new FooServlet());
fooHolder.setName("foo");
@@ -73,19 +75,22 @@ public class TestQuickStart
lholder.setListener(new FooContextListener());
quickstart.getServletHandler().addListener(lholder);
server.setHandler(quickstart);
+ server.setDryRun(true);
server.start();
- server.stop();
assertTrue(quickstartXml.exists());
//now run the webapp again purely from the generated quickstart
- QuickStartWebApp webapp = new QuickStartWebApp();
+ WebAppContext webapp = new WebAppContext();
webapp.setResourceBase(testDir.getAbsolutePath());
- webapp.setMode(QuickStartConfiguration.Mode.QUICKSTART);
+ webapp.addConfiguration(new QuickStartConfiguration());
+ webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
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("/").getResource();
@@ -104,28 +109,30 @@ public class TestQuickStart
Server server = new Server();
// generate a quickstart-web.xml
- QuickStartWebApp quickstart = new QuickStartWebApp();
+ WebAppContext quickstart = new WebAppContext();
quickstart.setResourceBase(testDir.getAbsolutePath());
- quickstart.setMode(QuickStartConfiguration.Mode.GENERATE);
- quickstart.setGenerateOrigin(true);
+ quickstart.addConfiguration(new QuickStartConfiguration());
+ quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.GENERATE);
+ quickstart.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "origin");
quickstart.setDescriptor(MavenTestingUtils.getTestResourceFile("web.xml").getAbsolutePath());
quickstart.setContextPath("/foo");
server.setHandler(quickstart);
+ server.setDryRun(true);
server.start();
-
assertEquals("/foo", quickstart.getContextPath());
assertFalse(quickstart.isContextPathDefault());
- server.stop();
assertTrue(quickstartXml.exists());
// quick start
- QuickStartWebApp webapp = new QuickStartWebApp();
+ WebAppContext webapp = new WebAppContext();
+ webapp.addConfiguration(new QuickStartConfiguration());
+ quickstart.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setResourceBase(testDir.getAbsolutePath());
- webapp.setMode(QuickStartConfiguration.Mode.QUICKSTART);
webapp.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
server.setHandler(webapp);
+ server.setDryRun(false);
server.start();
// verify the context path is the default-context-path
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
index 8b99979b18b..5543fe87158 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
@@ -23,7 +23,6 @@ import java.io.ObjectInputStream;
import java.io.Serializable;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
-import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
@@ -113,16 +112,4 @@ public class SessionAuthentication extends AbstractUserAuthentication
_session = se.getSession();
}
}
-
- @Override
- @Deprecated
- public void valueBound(HttpSessionBindingEvent event)
- {
- }
-
- @Override
- @Deprecated
- public void valueUnbound(HttpSessionBindingEvent event)
- {
- }
}
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java
new file mode 100644
index 00000000000..a30d3337524
--- /dev/null
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java
@@ -0,0 +1,93 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.eclipse.jetty.security.authentication.SessionAuthentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.security.Password;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * SessionAuthenticationTest
+ *
+ */
+public class SessionAuthenticationTest
+{
+ /**
+ * Check that a SessionAuthenticator is serializable, and that
+ * the deserialized SessionAuthenticator contains the same authentication
+ * and authorization information.
+ */
+ @Test
+ public void testSessionAuthenticationSerialization()
+ throws Exception
+ {
+
+ ContextHandler contextHandler = new ContextHandler();
+ SecurityHandler securityHandler = new ConstraintSecurityHandler();
+ contextHandler.setHandler(securityHandler);
+ TestLoginService loginService = new TestLoginService("SessionAuthTest");
+ Password pwd = new Password("foo");
+ loginService.putUser("foo", pwd, new String[]{"boss", "worker"});
+ securityHandler.setLoginService(loginService);
+ securityHandler.setAuthMethod("FORM");
+ UserIdentity user = loginService.login("foo", pwd, null);
+ assertNotNull(user);
+ assertNotNull(user.getUserPrincipal());
+ assertEquals("foo", user.getUserPrincipal().getName());
+ SessionAuthentication sessionAuth = new SessionAuthentication("FORM", user, pwd);
+ assertTrue(sessionAuth.isUserInRole(null, "boss"));
+ contextHandler.handle(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(sessionAuth);
+ oos.close();
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
+ SessionAuthentication reactivatedSessionAuth = (SessionAuthentication)ois.readObject();
+ assertNotNull(reactivatedSessionAuth);
+ assertNotNull(reactivatedSessionAuth.getUserIdentity());
+ assertNotNull(reactivatedSessionAuth.getUserIdentity().getUserPrincipal());
+ assertEquals("foo", reactivatedSessionAuth.getUserIdentity().getUserPrincipal().getName());
+ assertNotNull(reactivatedSessionAuth.getUserIdentity().getSubject());
+ assertTrue(reactivatedSessionAuth.isUserInRole(null, "boss"));
+ }
+ catch (Exception e)
+ {
+ fail(e);
+ }
+ }
+ });
+ }
+}
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index f63bf25c846..f0a1cf0a4a0 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -35,7 +35,11 @@
-
+
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml b/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml
index c40486e363e..35d7bfb28f0 100644
--- a/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml
+++ b/jetty-server/src/main/config/etc/sessions/session-cache-hash.xml
@@ -5,15 +5,16 @@
-
+
-
-
-
-
+
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/sessions/session-cache-null.xml b/jetty-server/src/main/config/etc/sessions/session-cache-null.xml
index 466402975aa..7de90393a52 100644
--- a/jetty-server/src/main/config/etc/sessions/session-cache-null.xml
+++ b/jetty-server/src/main/config/etc/sessions/session-cache-null.xml
@@ -10,13 +10,9 @@
-
-
-
-
-
-
-
+
+
+
diff --git a/jetty-server/src/main/config/modules/jdbc.mod b/jetty-server/src/main/config/modules/jdbc.mod
new file mode 100644
index 00000000000..a78fcc256aa
--- /dev/null
+++ b/jetty-server/src/main/config/modules/jdbc.mod
@@ -0,0 +1,4 @@
+DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
+
+[jpms]
+add-modules: java.sql
diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod
index be9bff04e0d..cb47011eb3e 100644
--- a/jetty-server/src/main/config/modules/server.mod
+++ b/jetty-server/src/main/config/modules/server.mod
@@ -80,3 +80,8 @@ etc/jetty.xml
## Dump the state of the Jetty server, components, and webapps before shutdown
# jetty.server.dumpBeforeStop=false
+
+## Scheduler Configuration
+# jetty.scheduler.name=
+# jetty.scheduler.deamon=false
+# jetty.scheduler.threads=-1
diff --git a/jetty-server/src/main/config/modules/session-cache-hash.mod b/jetty-server/src/main/config/modules/session-cache-hash.mod
index 32ab705c7a2..2d336bc1d99 100644
--- a/jetty-server/src/main/config/modules/session-cache-hash.mod
+++ b/jetty-server/src/main/config/modules/session-cache-hash.mod
@@ -1,10 +1,9 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
-Enable first level session cache in ConcurrentHashMap.
-If not enabled, sessions will use a HashSessionCache by default, so enabling
-via this module is only needed if the configuration properties need to be
-changed.
+Enable first level session cache. If this module is not enabled, sessions will
+use the DefaultSessionCache by default, so enabling via this module is only needed
+if the configuration properties need to be changed from their defaults.
[tags]
session
@@ -23,3 +22,4 @@ etc/sessions/session-cache-hash.xml
#jetty.session.saveOnInactiveEvict=false
#jetty.session.saveOnCreate=false
#jetty.session.removeUnloadableSessions=false
+#jetty.session.flushOnResponseCommit=false
diff --git a/jetty-server/src/main/config/modules/session-cache-null.mod b/jetty-server/src/main/config/modules/session-cache-null.mod
index 6069c8f8168..2a94f59cb82 100644
--- a/jetty-server/src/main/config/modules/session-cache-null.mod
+++ b/jetty-server/src/main/config/modules/session-cache-null.mod
@@ -18,4 +18,4 @@ etc/sessions/session-cache-null.xml
[ini-template]
#jetty.session.saveOnCreate=false
#jetty.session.removeUnloadableSessions=false
-#jetty.session.writeThroughMode=ON_EXIT
+#jetty.session.flushOnResponseCommit=false
diff --git a/jetty-server/src/main/config/modules/session-store-jdbc.mod b/jetty-server/src/main/config/modules/session-store-jdbc.mod
index 297cf2c69a4..e97457d0781 100644
--- a/jetty-server/src/main/config/modules/session-store-jdbc.mod
+++ b/jetty-server/src/main/config/modules/session-store-jdbc.mod
@@ -10,6 +10,7 @@ session
session-store
[depend]
+jdbc
sessions
sessions/jdbc/${db-connection-type}
@@ -54,7 +55,3 @@ db-connection-type=datasource
#jetty.session.jdbc.schema.maxIntervalColumn=maxInterval
#jetty.session.jdbc.schema.mapColumn=map
#jetty.session.jdbc.schema.table=JettySessions
-
-
-
-
diff --git a/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod b/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod
index be665ff9f3b..d30b911a3b9 100644
--- a/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod
+++ b/jetty-server/src/main/config/modules/sessions/jdbc/datasource.mod
@@ -3,5 +3,8 @@ DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-m
[description]
JDBC Datasource connections for session storage
+[depends]
+jdbc
+
[xml]
etc/sessions/jdbc/datasource.xml
diff --git a/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod b/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod
index eb7391a807d..ad387114cd6 100644
--- a/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod
+++ b/jetty-server/src/main/config/modules/sessions/jdbc/driver.mod
@@ -3,5 +3,8 @@ DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-m
[description]
JDBC Driver connections for session storage
+[depend]
+jdbc
+
[xml]
etc/sessions/jdbc/driver.xml
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index 1723bcc73df..d076742d121 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -52,7 +52,7 @@ import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.Locker;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadPoolBudget;
@@ -144,8 +144,8 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
protected static final Logger LOG = Log.getLogger(AbstractConnector.class);
- private final Locker _locker = new Locker();
- private final Condition _setAccepting = _locker.newCondition();
+ private final AutoLock _lock = new AutoLock();
+ private final Condition _setAccepting = _lock.newCondition();
private final Map _factories = new LinkedHashMap<>(); // Order is important on server side, so we use a LinkedHashMap
private final Server _server;
private final Executor _executor;
@@ -231,7 +231,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
* Get the {@link HttpChannel.Listener}s added to the connector
* as a single combined Listener.
* This is equivalent to a listener that iterates over the individual
- * listeners returned from getBeans(HttpChannel.Listener.class);
,
+ * listeners returned from {@code getBeans(HttpChannel.Listener.class);},
* except that:
* - The result is precomputed, so it is more efficient
* - The result is ordered by the order added.
@@ -332,7 +332,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
protected void interruptAcceptors()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
for (Thread thread : _acceptors)
{
@@ -387,7 +387,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void join(long timeout) throws InterruptedException
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
for (Thread thread : _acceptors)
{
@@ -404,7 +404,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
*/
public boolean isAccepting()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _accepting;
}
@@ -412,7 +412,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void setAccepting(boolean accepting)
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
_accepting = accepting;
_setAccepting.signalAll();
@@ -422,7 +422,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
public ConnectionFactory getConnectionFactory(String protocol)
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _factories.get(StringUtil.asciiToLowerCase(protocol));
}
@@ -431,7 +431,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
public T getConnectionFactory(Class factoryType)
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
for (ConnectionFactory f : _factories.values())
{
@@ -683,7 +683,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
while (isRunning())
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
if (!_accepting && isRunning())
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 55d155601ad..82801769c1a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -44,7 +44,6 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.QuietException;
@@ -251,12 +250,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
return _configuration;
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return getHttpTransport().isOptimizedForDirectBuffers();
- }
-
public Server getServer()
{
return _connector.getServer();
@@ -858,15 +851,15 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (response == null)
response = _response.newResponseMetaData();
commit(response);
-
+ _combinedListener.onResponseBegin(_request);
+ _request.onResponseCommit();
+
// wrap callback to process 100 responses
final int status = response.getStatus();
final Callback committed = (status < HttpStatus.OK_200 && status >= HttpStatus.CONTINUE_100)
? new Send100Callback(callback)
: new SendCallback(callback, content, true, complete);
- _combinedListener.onResponseBegin(_request);
-
// committing write
_transport.send(_request.getMetaData(), response, content, complete, committed);
}
@@ -971,12 +964,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
return _connector.getScheduler();
}
- /**
- * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
- */
- public boolean useDirectBuffers()
+ public boolean isUseOutputDirectByteBuffers()
{
- return getEndPoint() instanceof ChannelEndPoint;
+ return getHttpConfiguration().isUseOutputDirectByteBuffers();
}
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
index c09d71b7893..7fd7799954f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
@@ -69,6 +69,12 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
_metadata.setURI(new HttpURI());
}
+ @Override
+ public boolean isUseOutputDirectByteBuffers()
+ {
+ return _httpConnection.isUseOutputDirectByteBuffers();
+ }
+
@Override
protected HttpInput newHttpInput(HttpChannelState state)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
index 619f5741a57..5452c7ffa8b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
@@ -67,7 +67,8 @@ public class HttpConfiguration implements Dumpable
private boolean _delayDispatchUntilContent = true;
private boolean _persistentConnectionsEnabled = true;
private int _maxErrorDispatches = 10;
- private boolean _useDirectByteBuffers = false;
+ private boolean _useInputDirectByteBuffers = true;
+ private boolean _useOutputDirectByteBuffers = true;
private long _minRequestDataRate;
private long _minResponseDataRate;
private HttpCompliance _httpCompliance = HttpCompliance.RFC7230;
@@ -134,7 +135,8 @@ public class HttpConfiguration implements Dumpable
_delayDispatchUntilContent = config._delayDispatchUntilContent;
_persistentConnectionsEnabled = config._persistentConnectionsEnabled;
_maxErrorDispatches = config._maxErrorDispatches;
- _useDirectByteBuffers = config._useDirectByteBuffers;
+ _useInputDirectByteBuffers = config._useInputDirectByteBuffers;
+ _useOutputDirectByteBuffers = config._useOutputDirectByteBuffers;
_minRequestDataRate = config._minRequestDataRate;
_minResponseDataRate = config._minResponseDataRate;
_httpCompliance = config._httpCompliance;
@@ -327,17 +329,31 @@ public class HttpConfiguration implements Dumpable
}
/**
- * @param useDirectByteBuffers if true, use direct byte buffers for requests
+ * @param useInputDirectByteBuffers whether to use direct ByteBuffers for reading
*/
- public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
+ public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
{
- _useDirectByteBuffers = useDirectByteBuffers;
+ _useInputDirectByteBuffers = useInputDirectByteBuffers;
}
- @ManagedAttribute("Whether to use direct byte buffers for requests")
- public boolean isUseDirectByteBuffers()
+ @ManagedAttribute("Whether to use direct ByteBuffers for reading")
+ public boolean isUseInputDirectByteBuffers()
{
- return _useDirectByteBuffers;
+ return _useInputDirectByteBuffers;
+ }
+
+ /**
+ * @param useOutputDirectByteBuffers whether to use direct ByteBuffers for writing
+ */
+ public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
+ {
+ _useOutputDirectByteBuffers = useOutputDirectByteBuffers;
+ }
+
+ @ManagedAttribute("Whether to use direct ByteBuffers for writing")
+ public boolean isUseOutputDirectByteBuffers()
+ {
+ return _useOutputDirectByteBuffers;
}
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 9d8ac9edc35..fc52d201d9f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -72,6 +72,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
private final boolean _recordHttpComplianceViolations;
private final LongAdder bytesIn = new LongAdder();
private final LongAdder bytesOut = new LongAdder();
+ private boolean _useInputDirectByteBuffers;
+ private boolean _useOutputDirectByteBuffers;
/**
* Get the current connection that this thread is dispatched to.
@@ -163,12 +165,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return _generator;
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return getEndPoint().isOptimizedForDirectBuffers();
- }
-
@Override
public long getMessagesIn()
{
@@ -181,6 +177,26 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return getHttpChannel().getRequests();
}
+ public boolean isUseInputDirectByteBuffers()
+ {
+ return _useInputDirectByteBuffers;
+ }
+
+ public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
+ {
+ _useInputDirectByteBuffers = useInputDirectByteBuffers;
+ }
+
+ public boolean isUseOutputDirectByteBuffers()
+ {
+ return _useOutputDirectByteBuffers;
+ }
+
+ public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
+ {
+ _useOutputDirectByteBuffers = useOutputDirectByteBuffers;
+ }
+
@Override
public ByteBuffer onUpgradeFrom()
{
@@ -223,7 +239,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public ByteBuffer getRequestBuffer()
{
if (_requestBuffer == null)
- _requestBuffer = _bufferPool.acquire(getInputBufferSize(), _config.isUseDirectByteBuffers());
+ {
+ boolean useDirectByteBuffers = isUseInputDirectByteBuffers();
+ _requestBuffer = _bufferPool.acquire(getInputBufferSize(), useDirectByteBuffers);
+ }
return _requestBuffer;
}
@@ -253,11 +272,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// Parse the request buffer.
boolean handle = parseRequestBuffer();
- // If there was a connection upgrade, the other
- // connection took over, nothing more to do here.
- if (getEndPoint().getConnection() != this)
- break;
-
// Handle channel event
if (handle)
{
@@ -731,14 +745,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
if (_callback == null)
throw new IllegalStateException();
+ boolean useDirectByteBuffers = isUseOutputDirectByteBuffers();
ByteBuffer chunk = _chunk;
while (true)
{
HttpGenerator.Result result = _generator.generateResponse(_info, _head, _header, chunk, _content, _lastContent);
if (LOG.isDebugEnabled())
- LOG.debug("{} generate: {} ({},{},{})@{}",
- this,
+ LOG.debug("generate: {} for {} ({},{},{})@{}",
result,
+ this,
BufferUtil.toSummaryString(_header),
BufferUtil.toSummaryString(_content),
_lastContent,
@@ -751,19 +766,19 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
case NEED_HEADER:
{
- _header = _bufferPool.acquire(_config.getResponseHeaderSize(), _config.isUseDirectByteBuffers());
+ _header = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers);
continue;
}
case NEED_CHUNK:
{
- chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, _config.isUseDirectByteBuffers());
+ chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers);
continue;
}
case NEED_CHUNK_TRAILER:
{
if (_chunk != null)
_bufferPool.release(_chunk);
- chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), _config.isUseDirectByteBuffers());
+ chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers);
continue;
}
case FLUSH:
@@ -829,8 +844,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
case DONE:
{
- // If shutdown after commit, we can still close here.
- if (getConnector().isShutdown())
+ // If this is the end of the response and the connector was shutdown after response was committed,
+ // we can't add the Connection:close header, but we are still allowed to close the connection
+ // by shutting down the output.
+ if (getConnector().isShutdown() && _generator.isEnd() && _generator.isPersistent())
_shutdownOut = true;
return Action.SUCCEEDED;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
index beee4fbe7a5..fc078ab1d90 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.server;
+import java.util.Objects;
+
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -32,7 +34,9 @@ import org.eclipse.jetty.util.annotation.Name;
public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
{
private final HttpConfiguration _config;
- private boolean _recordHttpComplianceViolations = false;
+ private boolean _recordHttpComplianceViolations;
+ private boolean _useInputDirectByteBuffers;
+ private boolean _useOutputDirectByteBuffers;
public HttpConnectionFactory()
{
@@ -42,10 +46,10 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements
public HttpConnectionFactory(@Name("config") HttpConfiguration config)
{
super(HttpVersion.HTTP_1_1.asString());
- _config = config;
- if (config == null)
- throw new IllegalArgumentException("Null HttpConfiguration");
+ _config = Objects.requireNonNull(config);
addBean(_config);
+ setUseInputDirectByteBuffers(_config.isUseInputDirectByteBuffers());
+ setUseOutputDirectByteBuffers(_config.isUseOutputDirectByteBuffers());
}
@Override
@@ -59,15 +63,37 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements
return _recordHttpComplianceViolations;
}
- @Override
- public Connection newConnection(Connector connector, EndPoint endPoint)
- {
- HttpConnection conn = new HttpConnection(_config, connector, endPoint, isRecordHttpComplianceViolations());
- return configure(conn, connector, endPoint);
- }
-
public void setRecordHttpComplianceViolations(boolean recordHttpComplianceViolations)
{
this._recordHttpComplianceViolations = recordHttpComplianceViolations;
}
+
+ public boolean isUseInputDirectByteBuffers()
+ {
+ return _useInputDirectByteBuffers;
+ }
+
+ public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
+ {
+ _useInputDirectByteBuffers = useInputDirectByteBuffers;
+ }
+
+ public boolean isUseOutputDirectByteBuffers()
+ {
+ return _useOutputDirectByteBuffers;
+ }
+
+ public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
+ {
+ _useOutputDirectByteBuffers = useOutputDirectByteBuffers;
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ HttpConnection connection = new HttpConnection(_config, connector, endPoint, isRecordHttpComplianceViolations());
+ connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers());
+ connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers());
+ return configure(connection, connector, endPoint);
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index 50089ae163c..88c7d839faf 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -127,14 +127,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
Interceptor getNextInterceptor();
- /**
- * @return True if the Interceptor is optimized to receive direct
- * {@link ByteBuffer}s in the {@link #write(ByteBuffer, boolean, Callback)}
- * method. If false is returned, then passing direct buffers may cause
- * inefficiencies.
- */
- boolean isOptimizedForDirectBuffers();
-
/**
* Reset the buffers.
* If the Interceptor contains buffers then reset them.
@@ -417,7 +409,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
public ByteBuffer acquireBuffer()
{
if (_aggregate == null)
- _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.isUseOutputDirectByteBuffers());
return _aggregate;
}
@@ -591,17 +583,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// handle blocking write
// Should we aggregate?
- int capacity = getBufferSize();
+ // Yes - if the write is smaller than the commitSize (==aggregate buffer size)
+ // and the write is not the last one, or is last but will fit in an already allocated aggregate buffer.
boolean last = isLastContentToWrite(len);
- if (!last && len <= _commitSize)
+ if (len <= _commitSize && (!last || len <= BufferUtil.space(_aggregate)))
{
acquireBuffer();
// YES - fill the aggregate with content from the buffer
int filled = BufferUtil.fill(_aggregate, b, off, len);
- // return if we are not complete, not full and filled all the content
- if (filled == len && !BufferUtil.isFull(_aggregate))
+ // return if we are not the last write and have aggregated all of the content
+ if (!last && filled == len && !BufferUtil.isFull(_aggregate))
return;
// adjust offset/length
@@ -1065,7 +1058,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break;
}
- ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null;
+ ByteBuffer buffer = _channel.isUseOutputDirectByteBuffers() ? httpContent.getDirectBuffer() : null;
if (buffer == null)
buffer = httpContent.getIndirectBuffer();
@@ -1483,6 +1476,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
super(callback);
_in = in;
+ // Reading from InputStream requires byte[], don't use direct buffers.
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
}
@@ -1535,7 +1529,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
* An iterating callback that will take content from a
* ReadableByteChannel and write it to the {@link HttpChannel}.
* A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
- * {@link HttpChannel#useDirectBuffers()} is true.
+ * {@link HttpChannel#isUseOutputDirectByteBuffers()} is true.
* This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
* be notified as each buffer is written and only once all the input is consumed will the
* wrapped {@link Callback#succeeded()} method be called.
@@ -1550,7 +1544,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
super(callback);
_in = in;
- _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+ _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.isUseOutputDirectByteBuffers());
}
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
index ace983bd311..88d5b58d7c9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
@@ -73,11 +73,4 @@ public interface HttpTransport
* @param failure the failure that caused the abort.
*/
void abort(Throwable failure);
-
- /**
- * Is the underlying transport optimized for DirectBuffer usage
- *
- * @return True if direct buffers can be used optimally.
- */
- boolean isOptimizedForDirectBuffers();
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
index 43261cd5c1b..308be3bb929 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
@@ -601,12 +601,6 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
_local = local;
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return _endp.isOptimizedForDirectBuffers();
- }
-
@Override
public InetSocketAddress getLocalAddress()
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 55444641720..4471850c5d6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -230,7 +230,7 @@ public class Request implements HttpServletRequest
private long _timeStamp;
private MultiParts _multiParts; //if the request is a multi-part mime
private AsyncContextState _async;
- private List _sessions; //list of sessions used during lifetime of request
+ private List _sessions; //list of sessions used during lifetime of request
public Request(HttpChannel channel, HttpInput input)
{
@@ -363,32 +363,41 @@ public class Request implements HttpServletRequest
*/
public void enterSession(HttpSession s)
{
- if (s == null)
+ if (!(s instanceof Session))
return;
if (_sessions == null)
_sessions = new ArrayList<>();
if (LOG.isDebugEnabled())
LOG.debug("Request {} entering session={}", this, s);
- _sessions.add(s);
+ _sessions.add((Session)s);
}
/**
* Complete this request's access to a session.
*
- * @param s the session
+ * @param session the session
*/
- private void leaveSession(HttpSession s)
+ private void leaveSession(Session session)
{
- if (s == null)
- return;
-
- Session session = (Session)s;
if (LOG.isDebugEnabled())
LOG.debug("Request {} leaving session {}", this, session);
session.getSessionHandler().complete(session);
}
+ /**
+ * A response is being committed for a session,
+ * potentially write the session out before the
+ * client receives the response.
+ * @param session the session
+ */
+ private void commitSession(Session session)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response {} committing for session {}", this, session);
+ session.getSessionHandler().commit(session);
+ }
+
private MultiMap getParameters()
{
if (!_contentParamsExtracted)
@@ -1490,13 +1499,26 @@ public class Request implements HttpServletRequest
*/
public void onCompleted()
{
- if (_sessions != null && _sessions.size() > 0)
+ if (_sessions != null)
{
- for (HttpSession s:_sessions)
+ for (Session s:_sessions)
leaveSession(s);
}
}
+ /**
+ * Called when a response is about to be committed, ie sent
+ * back to the client
+ */
+ public void onResponseCommit()
+ {
+ if (_sessions != null)
+ {
+ for (Session s:_sessions)
+ commitSession(s);
+ }
+ }
+
/**
* Find a session that this request has already entered for the
* given SessionHandler
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
index 282baf968c1..8456899589a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -57,7 +57,7 @@ import org.eclipse.jetty.util.component.AttributeContainerMap;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Locker;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;
@@ -79,12 +79,12 @@ public class Server extends HandlerWrapper implements Attributes
private final List _connectors = new CopyOnWriteArrayList<>();
private SessionIdManager _sessionIdManager;
private boolean _stopAtShutdown;
- private boolean _dumpAfterStart = false;
- private boolean _dumpBeforeStop = false;
+ private boolean _dumpAfterStart;
+ private boolean _dumpBeforeStop;
private ErrorHandler _errorHandler;
private RequestLog _requestLog;
-
- private final Locker _dateLocker = new Locker();
+ private boolean _dryRun;
+ private final AutoLock _dateLock = new AutoLock();
private volatile DateField _dateField;
public Server()
@@ -131,6 +131,16 @@ public class Server extends HandlerWrapper implements Attributes
setServer(this);
}
+ public boolean isDryRun()
+ {
+ return _dryRun;
+ }
+
+ public void setDryRun(boolean dryRun)
+ {
+ _dryRun = dryRun;
+ }
+
public RequestLog getRequestLog()
{
return _requestLog;
@@ -315,7 +325,7 @@ public class Server extends HandlerWrapper implements Attributes
if (df == null || df._seconds != seconds)
{
- try (Locker.Lock lock = _dateLocker.lock())
+ try (AutoLock lock = _dateLock.lock())
{
df = _dateField;
if (df == null || df._seconds != seconds)
@@ -367,25 +377,33 @@ public class Server extends HandlerWrapper implements Attributes
MultiException mex = new MultiException();
// Open network connector to ensure ports are available
- _connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector ->
+ if (!_dryRun)
{
- try
+ _connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector ->
{
- connector.open();
- }
- catch (Throwable th)
- {
- mex.add(th);
- }
- });
-
- // Throw now if verified start sequence and there was an open exception
- mex.ifExceptionThrow();
+ try
+ {
+ connector.open();
+ }
+ catch (Throwable th)
+ {
+ mex.add(th);
+ }
+ });
+ // Throw now if verified start sequence and there was an open exception
+ mex.ifExceptionThrow();
+ }
// Start the server and components, but not connectors!
// #start(LifeCycle) is overridden so that connectors are not started
super.doStart();
+ if (_dryRun)
+ {
+ LOG.info(String.format("Started(dry run) %s @%dms", this, Uptime.getUptime()));
+ throw new StopException();
+ }
+
// start connectors
for (Connector connector : _connectors)
{
@@ -402,7 +420,7 @@ public class Server extends HandlerWrapper implements Attributes
}
mex.ifExceptionThrow();
- LOG.info(String.format("Started @%dms", Uptime.getUptime()));
+ LOG.info(String.format("Started %s @%dms", this, Uptime.getUptime()));
}
catch (Throwable th)
{
@@ -423,7 +441,7 @@ public class Server extends HandlerWrapper implements Attributes
}
finally
{
- if (isDumpAfterStart())
+ if (isDumpAfterStart() && !(_dryRun && isDumpBeforeStop()))
dumpStdErr();
}
}
@@ -442,6 +460,7 @@ public class Server extends HandlerWrapper implements Attributes
if (isDumpBeforeStop())
dumpStdErr();
+ LOG.info(String.format("Stopped %s", this));
if (LOG.isDebugEnabled())
LOG.debug("doStop {}", this);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
index cb2e2257eb7..b02064d54a3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
@@ -123,7 +123,7 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
if (_server == server)
return;
if (isStarted())
- throw new IllegalStateException(STARTED);
+ throw new IllegalStateException(getState());
_server = server;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
index e56645f5dcf..5f533d6f549 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
@@ -128,7 +128,7 @@ public abstract class AbstractHandlerContainer extends AbstractHandler implement
return;
if (isStarted())
- throw new IllegalStateException(STARTED);
+ throw new IllegalStateException(getState());
super.setServer(server);
Handler[] handlers = getHandlers();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
index bbb24c536b0..8c28bcdd7b9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
@@ -271,12 +271,6 @@ public class BufferedResponseHandler extends HandlerWrapper
return _next;
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return false;
- }
-
protected void commit(Queue buffers, Callback callback)
{
// If only 1 buffer
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index 4840dbeff33..6f2b3c563aa 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -126,7 +126,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
- private static final String __unimplmented = "Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+ private static final String UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER = "Unimplemented {} - use org.eclipse.jetty.servlet.ServletContextHandler";
private static final Logger LOG = Log.getLogger(ContextHandler.class);
@@ -889,9 +889,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
protected void stopContext() throws Exception
{
- // stop all the handler hierarchy
- super.doStop();
-
// Call the context listeners
ServletContextEvent event = new ServletContextEvent(_scontext);
Collections.reverse(_destroySerletContextListeners);
@@ -907,6 +904,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
ex.add(x);
}
}
+
+ // stop all the handler hierarchy
+ try
+ {
+ super.doStop();
+ }
+ catch (Exception x)
+ {
+ ex.add(x);
+ }
+
ex.ifExceptionThrow();
}
@@ -2231,7 +2239,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public void log(String message, Throwable throwable)
{
- _logger.warn(message, throwable);
+ if (throwable == null)
+ _logger.warn(message);
+ else
+ _logger.warn(message, throwable);
}
/*
@@ -2491,7 +2502,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public JspConfigDescriptor getJspConfigDescriptor()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()");
return null;
}
@@ -2684,138 +2695,141 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public Dynamic addFilter(String filterName, Class extends Filter> filterClass)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Class)");
return null;
}
@Override
public Dynamic addFilter(String filterName, Filter filter)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Filter)");
return null;
}
@Override
public Dynamic addFilter(String filterName, String className)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, String)");
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class extends Servlet> servletClass)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Class)");
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Servlet)");
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, String)");
return null;
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile)
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addJspFile(String, String)");
return null;
}
@Override
public T createFilter(Class c) throws ServletException
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createFilter(Class)");
return null;
}
@Override
public T createServlet(Class c) throws ServletException
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createServlet(Class)");
return null;
}
@Override
public Set getDefaultSessionTrackingModes()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getDefaultSessionTrackingModes()");
return null;
}
@Override
public Set getEffectiveSessionTrackingModes()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getEffectiveSessionTrackingModes()");
return null;
}
@Override
public FilterRegistration getFilterRegistration(String filterName)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistration(String)");
return null;
}
@Override
public Map getFilterRegistrations()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistrations()");
return null;
}
@Override
public ServletRegistration getServletRegistration(String servletName)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistration(String)");
return null;
}
@Override
public Map getServletRegistrations()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistrations()");
return null;
}
@Override
public SessionCookieConfig getSessionCookieConfig()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionCookieConfig()");
return null;
}
@Override
public void setSessionTrackingModes(Set sessionTrackingModes)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTrackingModes(Set)");
}
@Override
public void addListener(String className)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(String)");
}
@Override
public void addListener(T t)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(T)");
}
@Override
public void addListener(Class extends EventListener> listenerClass)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(Class)");
}
@Override
@@ -2862,14 +2876,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public JspConfigDescriptor getJspConfigDescriptor()
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()");
return null;
}
@Override
public void declareRoles(String... roleNames)
{
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "declareRoles(String...)");
}
@Override
@@ -2878,49 +2892,67 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return null;
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public int getSessionTimeout()
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionTimeout()");
return 0;
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public void setSessionTimeout(int sessionTimeout)
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTimeout(int)");
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public String getRequestCharacterEncoding()
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getRequestCharacterEncoding()");
return null;
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public void setRequestCharacterEncoding(String encoding)
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setRequestCharacterEncoding(String)");
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public String getResponseCharacterEncoding()
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getResponseCharacterEncoding()");
return null;
}
+ /**
+ * @since Servlet 4.0
+ */
@Override
public void setResponseCharacterEncoding(String encoding)
{
// TODO new in 4.0
- LOG.warn(__unimplmented);
+ LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setResponseCharacterEncoding(String)");
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
index dfea9fc93b7..9bbda66b116 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
@@ -82,7 +82,7 @@ public class HandlerCollection extends AbstractHandlerContainer
public void setHandlers(Handler[] handlers)
{
if (!_mutableWhenRunning && isStarted())
- throw new IllegalStateException(STARTED);
+ throw new IllegalStateException(getState());
while (true)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
index 27a53fecf98..0c0897f5c1f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
@@ -74,7 +74,7 @@ public class HandlerWrapper extends AbstractHandlerContainer
public void setHandler(Handler handler)
{
if (isStarted())
- throw new IllegalStateException(STARTED);
+ throw new IllegalStateException(getState());
// check for loops
if (handler == this || (handler instanceof HandlerContainer &&
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java
index 7666bcb0641..feab2f5644a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java
@@ -48,7 +48,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Locker;
+import org.eclipse.jetty.util.thread.AutoLock;
/**
* Handler to limit the threads per IP address for DOS protection
@@ -241,7 +241,7 @@ public class ThreadLimitHandler extends HandlerWrapper
}
}
- protected Remote getRemote(Request baseRequest)
+ private Remote getRemote(Request baseRequest)
{
Remote remote = (Remote)baseRequest.getAttribute(REMOTE);
if (remote != null)
@@ -329,11 +329,11 @@ public class ThreadLimitHandler extends HandlerWrapper
return (comma >= 0) ? forwardedFor.substring(comma + 1).trim() : forwardedFor;
}
- private final class Remote implements Closeable
+ private static final class Remote implements Closeable
{
private final String _ip;
private final int _limit;
- private final Locker _locker = new Locker();
+ private final AutoLock _lock = new AutoLock();
private int _permits;
private Deque> _queue = new ArrayDeque<>();
private final CompletableFuture _permitted = CompletableFuture.completedFuture(this);
@@ -346,7 +346,7 @@ public class ThreadLimitHandler extends HandlerWrapper
public CompletableFuture acquire()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
// Do we have available passes?
if (_permits < _limit)
@@ -358,16 +358,16 @@ public class ThreadLimitHandler extends HandlerWrapper
}
// No pass available, so queue a new future
- CompletableFuture pass = new CompletableFuture();
+ CompletableFuture pass = new CompletableFuture<>();
_queue.addLast(pass);
return pass;
}
}
@Override
- public void close() throws IOException
+ public void close()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
// reduce the allocated passes
_permits--;
@@ -396,14 +396,14 @@ public class ThreadLimitHandler extends HandlerWrapper
@Override
public String toString()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return String.format("R[ip=%s,p=%d,l=%d,q=%d]", _ip, _permits, _limit, _queue.size());
}
}
}
- private final class RFC7239 extends QuotedCSV
+ private static final class RFC7239 extends QuotedCSV
{
String _for;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
index 5c590b2d3e5..bb1022bbb7b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
@@ -422,7 +423,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
@Override
public Deflater getDeflater(Request request, long contentLength)
{
- String ua = request.getHttpFields().get(HttpHeader.USER_AGENT);
+ HttpFields httpFields = request.getHttpFields();
+ String ua = httpFields.get(HttpHeader.USER_AGENT);
if (ua != null && !isAgentGzipable(ua))
{
LOG.debug("{} excluded user agent {}", this, request);
@@ -436,16 +438,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
// check the accept encoding header
- HttpField accept = request.getHttpFields().getField(HttpHeader.ACCEPT_ENCODING);
-
- if (accept == null)
- {
- LOG.debug("{} excluded !accept {}", this, request);
- return null;
- }
- boolean gzip = accept.contains("gzip");
-
- if (!gzip)
+ if (!httpFields.contains(HttpHeader.ACCEPT_ENCODING, "gzip"))
{
LOG.debug("{} excluded not gzip accept {}", this, request);
return null;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
index 4a582348277..e1178fe2788 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
@@ -93,12 +93,6 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
return _interceptor;
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return false; // No point as deflator is in user space.
- }
-
@Override
public void write(ByteBuffer content, boolean complete, Callback callback)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java
index 11b1ec7453b..329a44bec48 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java
@@ -29,7 +29,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Locker.Lock;
+import org.eclipse.jetty.util.thread.AutoLock;
/**
* AbstractSessionCache
@@ -91,6 +91,12 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
* deleted from the SessionDataStore.
*/
protected boolean _removeUnloadableSessions;
+
+ /**
+ * If true, when a response is about to be committed back to the client,
+ * a dirty session will be flushed to the session store.
+ */
+ protected boolean _flushOnResponseCommit;
/**
* Create a new Session object from pre-existing session data
@@ -110,9 +116,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
*/
public abstract Session newSession(HttpServletRequest request, SessionData data);
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#newSession(javax.servlet.http.HttpServletRequest, java.lang.String, long, long)
- */
@Override
public Session newSession(HttpServletRequest request, String id, long time, long maxInactiveMs)
{
@@ -169,7 +172,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
/**
* PlaceHolder
*/
- protected class PlaceHolderSession extends Session
+ protected static class PlaceHolderSession extends Session
{
/**
@@ -199,9 +202,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
return _handler;
}
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#initialize(org.eclipse.jetty.server.session.SessionContext)
- */
@Override
public void initialize(SessionContext context)
{
@@ -248,9 +248,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
return _sessionDataStore;
}
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#setSessionDataStore(org.eclipse.jetty.server.session.SessionDataStore)
- */
@Override
public void setSessionDataStore(SessionDataStore sessionStore)
{
@@ -258,9 +255,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
_sessionDataStore = sessionStore;
}
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#getEvictionPolicy()
- */
@ManagedAttribute(value = "session eviction policy", readonly = true)
@Override
public int getEvictionPolicy()
@@ -272,8 +266,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
* -1 means we never evict inactive sessions.
* 0 means we evict a session after the last request for it exits
* >0 is the number of seconds after which we evict inactive sessions from the cache
- *
- * @see org.eclipse.jetty.server.session.SessionCache#setEvictionPolicy(int)
*/
@Override
public void setEvictionPolicy(int evictionTimeout)
@@ -308,7 +300,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
* If a session's data cannot be loaded from the store without error, remove
* it from the persistent store.
*
- * @param removeUnloadableSessions if true
unloadable sessions will be removed from session store
+ * @param removeUnloadableSessions if {@code true} unloadable sessions will be removed from session store
*/
@Override
public void setRemoveUnloadableSessions(boolean removeUnloadableSessions)
@@ -316,14 +308,24 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
_removeUnloadableSessions = removeUnloadableSessions;
}
+ @Override
+ public void setFlushOnResponseCommit(boolean flushOnResponseCommit)
+ {
+ _flushOnResponseCommit = flushOnResponseCommit;
+ }
+
+ @Override
+ public boolean isFlushOnResponseCommit()
+ {
+ return _flushOnResponseCommit;
+ }
+
/**
* Get a session object.
*
* If the session object is not in this session store, try getting
* the data for it from a SessionDataStore associated with the
* session manager. The usage count of the session is incremented.
- *
- * @see org.eclipse.jetty.server.session.SessionCache#get(java.lang.String)
*/
@Override
public Session get(String id) throws Exception
@@ -339,8 +341,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
*
* @param id The session to retrieve
* @param enter if true, the usage count of the session will be incremented
- * @return
- * @throws Exception
+ * @return the Session object
+ * @throws Exception if the session cannot be retrieved
*/
protected Session getAndEnter(String id, boolean enter) throws Exception
{
@@ -361,7 +363,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
//didn't get a session, try and create one and put in a placeholder for it
PlaceHolderSession phs = new PlaceHolderSession(_handler, new SessionData(id, null, null, 0, 0, 0, 0));
- Lock phsLock = phs.lock();
+ AutoLock phsLock = phs.lock();
Session s = doPutIfAbsent(id, phs);
if (s == null)
{
@@ -377,7 +379,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
break;
}
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
//swap it in instead of the placeholder
boolean success = doReplace(id, phs, session);
@@ -414,7 +416,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
{
//my placeholder didn't win, check the session returned
phsLock.close();
- try (Lock lock = s.lock())
+ try (AutoLock lock = s.lock())
{
//is it a placeholder? or is a non-resident session? In both cases, chuck it away and start again
if (!s.isResident() || s instanceof PlaceHolderSession)
@@ -433,7 +435,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
else
{
//check the session returned
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
//is it a placeholder? or is it passivated? In both cases, chuck it away and start again
if (!session.isResident() || session instanceof PlaceHolderSession)
@@ -494,8 +496,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
* Add an entirely new session (created by the application calling Request.getSession(true))
* to the cache. The usage count of the fresh session is incremented.
*
- * @param id the id
- * @param session
+ * @param id the session id
+ * @param session the new session to add
*/
@Override
public void add(String id, Session session) throws Exception
@@ -503,7 +505,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
if (id == null || session == null)
throw new IllegalArgumentException("Add key=" + id + " session=" + (session == null ? "null" : session.getId()));
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
if (session.getSessionHandler() == null)
throw new IllegalStateException("Session " + id + " is not managed");
@@ -522,8 +524,41 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
}
/**
- * @deprecated
+ * A response that has accessed this session is about to
+ * be returned to the client. Pass the session to the store
+ * to persist, so that any changes will be visible to
+ * subsequent requests on the same node (if using NullSessionCache),
+ * or on other nodes.
*/
+ @Override
+ public void commit(Session session) throws Exception
+ {
+ if (session == null)
+ return;
+
+ try (AutoLock lock = session.lock())
+ {
+ //only write the session out at this point if the attributes changed. If only
+ //the lastAccess/expiry time changed defer the write until the last request exits
+ if (session.getSessionData().isDirty() && _flushOnResponseCommit)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Flush session {} on response commit", session);
+ //save the session
+ if (!_sessionDataStore.isPassivating())
+ {
+ _sessionDataStore.store(session.getId(), session.getSessionData());
+ }
+ else
+ {
+ session.willPassivate();
+ _sessionDataStore.store(session.getId(), session.getSessionData());
+ session.didActivate();
+ }
+ }
+ }
+ }
+
@Override
public void put(String id, Session session) throws Exception
{
@@ -542,8 +577,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
*
* If the evictionPolicy == SessionCache.EVICT_ON_SESSION_EXIT then after we have saved
* the session, we evict it from the cache.
- *
- * @see org.eclipse.jetty.server.session.SessionCache#release(java.lang.String, org.eclipse.jetty.server.session.Session)
*/
@Override
public void release(String id, Session session) throws Exception
@@ -551,7 +584,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
if (id == null || session == null)
throw new IllegalArgumentException("Put key=" + id + " session=" + (session == null ? "null" : session.getId()));
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
if (session.getSessionHandler() == null)
throw new IllegalStateException("Session " + id + " is not managed");
@@ -630,7 +663,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
* it will check with the data store.
*
* @throws Exception the Exception
- * @see org.eclipse.jetty.server.session.SessionCache#exists(java.lang.String)
*/
@Override
public boolean exists(String id) throws Exception
@@ -639,7 +671,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
Session s = doGet(id);
if (s != null)
{
- try (Lock lock = s.lock())
+ try (AutoLock lock = s.lock())
{
//wait for the lock and check the validity of the session
return s.isValid();
@@ -653,8 +685,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
/**
* Check to see if this cache contains an entry for the session
* corresponding to the session id.
- *
- * @see org.eclipse.jetty.server.session.SessionCache#contains(java.lang.String)
*/
@Override
public boolean contains(String id) throws Exception
@@ -665,8 +695,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
/**
* Remove a session object from this store and from any backing store.
- *
- * @see org.eclipse.jetty.server.session.SessionCache#delete(java.lang.String)
*/
@Override
public Session delete(String id) throws Exception
@@ -691,9 +719,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
return doDelete(id);
}
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#checkExpiration(Set)
- */
@Override
public Set checkExpiration(Set candidates)
{
@@ -741,7 +766,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
if (LOG.isDebugEnabled())
LOG.debug("Checking for idle {}", session.getId());
- try (Lock s = session.lock())
+ try (AutoLock lock = session.lock())
{
if (getEvictionPolicy() > 0 && session.isIdleLongerThan(getEvictionPolicy()) &&
session.isValid() && session.isResident() && session.getRequests() <= 0)
@@ -759,6 +784,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
if (_sessionDataStore.isPassivating())
session.willPassivate();
+ //Fake being dirty to force the write
+ session.getSessionData().setDirty(true);
_sessionDataStore.store(session.getId(), session.getSessionData());
}
@@ -768,7 +795,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
catch (Exception e)
{
LOG.warn("Passivation of idle session {} failed", session.getId(), e);
- //session.updateInactivityTimer();
}
}
}
@@ -803,7 +829,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
if (session == null)
return;
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
final String oldId = session.getId();
session.checkValidForWrite(); //can't change id on invalid session
@@ -826,9 +852,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
}
}
- /**
- * @see org.eclipse.jetty.server.session.SessionCache#setSaveOnInactiveEviction(boolean)
- */
@Override
public void setSaveOnInactiveEviction(boolean saveOnEvict)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java
new file mode 100644
index 00000000000..b29e5585a19
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCacheFactory.java
@@ -0,0 +1,114 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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.server.session;
+
+/**
+ * AbstractSessionCacheFactory
+ *
+ * Base class for SessionCacheFactories.
+ *
+ */
+public abstract class AbstractSessionCacheFactory implements SessionCacheFactory
+{
+ int _evictionPolicy;
+ boolean _saveOnInactiveEvict;
+ boolean _saveOnCreate;
+ boolean _removeUnloadableSessions;
+ boolean _flushOnResponseCommit;
+
+ /**
+ * @return the flushOnResponseCommit
+ */
+ public boolean isFlushOnResponseCommit()
+ {
+ return _flushOnResponseCommit;
+ }
+
+ /**
+ * @param flushOnResponseCommit the flushOnResponseCommit to set
+ */
+ public void setFlushOnResponseCommit(boolean flushOnResponseCommit)
+ {
+ _flushOnResponseCommit = flushOnResponseCommit;
+ }
+
+ /**
+ * @return the saveOnCreate
+ */
+ public boolean isSaveOnCreate()
+ {
+ return _saveOnCreate;
+ }
+
+ /**
+ * @param saveOnCreate the saveOnCreate to set
+ */
+ public void setSaveOnCreate(boolean saveOnCreate)
+ {
+ _saveOnCreate = saveOnCreate;
+ }
+
+ /**
+ * @return the removeUnloadableSessions
+ */
+ public boolean isRemoveUnloadableSessions()
+ {
+ return _removeUnloadableSessions;
+ }
+
+ /**
+ * @param removeUnloadableSessions the removeUnloadableSessions to set
+ */
+ public void setRemoveUnloadableSessions(boolean removeUnloadableSessions)
+ {
+ _removeUnloadableSessions = removeUnloadableSessions;
+ }
+
+ /**
+ * @return the evictionPolicy
+ */
+ public int getEvictionPolicy()
+ {
+ return _evictionPolicy;
+ }
+
+ /**
+ * @param evictionPolicy the evictionPolicy to set
+ */
+ public void setEvictionPolicy(int evictionPolicy)
+ {
+ _evictionPolicy = evictionPolicy;
+ }
+
+ /**
+ * @return the saveOnInactiveEvict
+ */
+ public boolean isSaveOnInactiveEvict()
+ {
+ return _saveOnInactiveEvict;
+ }
+
+ /**
+ * @param saveOnInactiveEvict the saveOnInactiveEvict to set
+ */
+ public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict)
+ {
+ _saveOnInactiveEvict = saveOnInactiveEvict;
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java
index 17ce67038ce..813a661f2d5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java
@@ -81,24 +81,20 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
public SessionData load(String id) throws Exception
{
if (!isStarted())
- throw new IllegalStateException ("Not started");
+ throw new IllegalStateException("Not started");
final AtomicReference reference = new AtomicReference();
final AtomicReference exception = new AtomicReference();
- Runnable r = new Runnable()
+ Runnable r = () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- reference.set(doLoad(id));
- }
- catch (Exception e)
- {
- exception.set(e);
- }
+ reference.set(doLoad(id));
+ }
+ catch (Exception e)
+ {
+ exception.set(e);
}
};
@@ -129,10 +125,14 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
long savePeriodMs = (_savePeriodSec <= 0 ? 0 : TimeUnit.SECONDS.toMillis(_savePeriodSec));
if (LOG.isDebugEnabled())
- LOG.debug("Store: id={}, dirty={}, lsave={}, period={}, elapsed={}", id, data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis() - lastSave));
+ {
+ LOG.debug("Store: id={}, mdirty={}, dirty={}, lsave={}, period={}, elapsed={}", id, data.isMetaDataDirty(),
+ data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis() - lastSave));
+ }
- //save session if attribute changed or never been saved or time between saves exceeds threshold
- if (data.isDirty() || (lastSave <= 0) || ((System.currentTimeMillis() - lastSave) >= savePeriodMs))
+ //save session if attribute changed, never been saved or metadata changed (eg expiry time) and save interval exceeded
+ if (data.isDirty() || (lastSave <= 0) ||
+ (data.isMetaDataDirty() && ((System.currentTimeMillis() - lastSave) >= savePeriodMs)))
{
//set the last saved time to now
data.setLastSaved(System.currentTimeMillis());
@@ -140,7 +140,7 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
{
//call the specific store method, passing in previous save time
doStore(id, data, lastSave);
- data.setDirty(false); //only undo the dirty setting if we saved it
+ data.clean(); //unset all dirty flags
}
catch (Exception e)
{
@@ -163,7 +163,7 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
public Set getExpired(Set candidates)
{
if (!isStarted())
- throw new IllegalStateException ("Not started");
+ throw new IllegalStateException("Not started");
try
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java
index b1261647414..87b945e5a0b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCacheFactory.java
@@ -23,80 +23,8 @@ package org.eclipse.jetty.server.session;
*
* Factory for creating new DefaultSessionCaches.
*/
-public class DefaultSessionCacheFactory implements SessionCacheFactory
+public class DefaultSessionCacheFactory extends AbstractSessionCacheFactory
{
- int _evictionPolicy;
- boolean _saveOnInactiveEvict;
- boolean _saveOnCreate;
- boolean _removeUnloadableSessions;
-
- /**
- * @return the saveOnCreate
- */
- public boolean isSaveOnCreate()
- {
- return _saveOnCreate;
- }
-
- /**
- * @param saveOnCreate the saveOnCreate to set
- */
- public void setSaveOnCreate(boolean saveOnCreate)
- {
- _saveOnCreate = saveOnCreate;
- }
-
- /**
- * @return the removeUnloadableSessions
- */
- public boolean isRemoveUnloadableSessions()
- {
- return _removeUnloadableSessions;
- }
-
- /**
- * @param removeUnloadableSessions the removeUnloadableSessions to set
- */
- public void setRemoveUnloadableSessions(boolean removeUnloadableSessions)
- {
- _removeUnloadableSessions = removeUnloadableSessions;
- }
-
- /**
- * @return the evictionPolicy
- */
- public int getEvictionPolicy()
- {
- return _evictionPolicy;
- }
-
- /**
- * @param evictionPolicy the evictionPolicy to set
- */
- public void setEvictionPolicy(int evictionPolicy)
- {
- _evictionPolicy = evictionPolicy;
- }
-
- /**
- * @return the saveOnInactiveEvict
- */
- public boolean isSaveOnInactiveEvict()
- {
- return _saveOnInactiveEvict;
- }
-
- /**
- * @param saveOnInactiveEvict the saveOnInactiveEvict to set
- */
- public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict)
- {
- _saveOnInactiveEvict = saveOnInactiveEvict;
- }
-
- /**
- * @see org.eclipse.jetty.server.session.SessionCacheFactory#getSessionCache(org.eclipse.jetty.server.session.SessionHandler)
- */
@Override
public SessionCache getSessionCache(SessionHandler handler)
{
@@ -105,6 +33,7 @@ public class DefaultSessionCacheFactory implements SessionCacheFactory
cache.setSaveOnInactiveEviction(isSaveOnInactiveEvict());
cache.setSaveOnCreate(isSaveOnCreate());
cache.setRemoveUnloadableSessions(isRemoveUnloadableSessions());
+ cache.setFlushOnResponseCommit(isFlushOnResponseCommit());
return cache;
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java
index e22919eed7b..c589aab4bdb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java
@@ -18,12 +18,7 @@
package org.eclipse.jetty.server.session;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSessionAttributeListener;
-import javax.servlet.http.HttpSessionBindingEvent;
/**
* NullSessionCache
@@ -35,151 +30,6 @@ import javax.servlet.http.HttpSessionBindingEvent;
*/
public class NullSessionCache extends AbstractSessionCache
{
- /**
- * If the writethrough mode is ALWAYS or NEW, then use an
- * attribute listener to ascertain when the attribute has changed.
- *
- */
- public class WriteThroughAttributeListener implements HttpSessionAttributeListener
- {
- Set _sessionsBeingWritten = ConcurrentHashMap.newKeySet();
-
- @Override
- public void attributeAdded(HttpSessionBindingEvent event)
- {
- doAttributeChanged(event);
- }
-
- @Override
- public void attributeRemoved(HttpSessionBindingEvent event)
- {
- doAttributeChanged(event);
- }
-
- @Override
- public void attributeReplaced(HttpSessionBindingEvent event)
- {
- doAttributeChanged(event);
- }
-
- private void doAttributeChanged(HttpSessionBindingEvent event)
- {
- if (_writeThroughMode == WriteThroughMode.ON_EXIT)
- return;
-
- Session session = (Session)event.getSession();
-
- SessionDataStore store = getSessionDataStore();
-
- if (store == null)
- return;
-
- if (_writeThroughMode == WriteThroughMode.ALWAYS || (_writeThroughMode == WriteThroughMode.NEW && session.isNew()))
- {
- //ensure that a call to willPassivate doesn't result in a passivation
- //listener removing an attribute, which would cause this listener to
- //be called again
- if (_sessionsBeingWritten.add(session))
- {
- try
- {
- //should hold the lock on the session, but as sessions are never shared
- //with the NullSessionCache, there can be no other thread modifying the
- //same session at the same time (although of course there can be another
- //request modifying its copy of the session data, so it is impossible
- //to guarantee the order of writes).
- if (store.isPassivating())
- session.willPassivate();
- store.store(session.getId(), session.getSessionData());
- if (store.isPassivating())
- session.didActivate();
- }
- catch (Exception e)
- {
- LOG.warn("Write through of {} failed", e);
- }
- finally
- {
- _sessionsBeingWritten.remove(session);
- }
- }
- }
- }
- }
-
- /**
- * Defines the circumstances a session will be written to the backing store.
- */
- public enum WriteThroughMode
- {
- /**
- * ALWAYS means write through every attribute change.
- */
- ALWAYS,
- /**
- * NEW means to write through every attribute change only
- * while the session is freshly created, ie its id has not yet been returned to the client
- */
- NEW,
- /**
- * ON_EXIT means write the session only when the request exits
- * (which is the default behaviour of AbstractSessionCache)
- */
- ON_EXIT
- }
-
- private WriteThroughMode _writeThroughMode = WriteThroughMode.ON_EXIT;
- protected WriteThroughAttributeListener _listener = null;
-
- /**
- * @return the writeThroughMode
- */
- public WriteThroughMode getWriteThroughMode()
- {
- return _writeThroughMode;
- }
-
- /**
- * @param writeThroughMode the writeThroughMode to set
- */
- public void setWriteThroughMode(WriteThroughMode writeThroughMode)
- {
- if (getSessionHandler() == null)
- throw new IllegalStateException("No SessionHandler");
-
- //assume setting null is the same as ON_EXIT
- if (writeThroughMode == null)
- {
- if (_listener != null)
- getSessionHandler().removeEventListener(_listener);
- _listener = null;
- _writeThroughMode = WriteThroughMode.ON_EXIT;
- return;
- }
-
- switch (writeThroughMode)
- {
- case ON_EXIT:
- {
- if (_listener != null)
- getSessionHandler().removeEventListener(_listener);
- _listener = null;
- break;
- }
- case NEW:
- case ALWAYS:
- {
- if (_listener == null)
- {
- _listener = new WriteThroughAttributeListener();
- getSessionHandler().addEventListener(_listener);
- }
- break;
- }
- }
- _writeThroughMode = writeThroughMode;
- }
-
/**
* @param handler The SessionHandler related to this SessionCache
*/
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java
index dd4a4cd0986..40bf5f45f9d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCacheFactory.java
@@ -18,75 +18,51 @@
package org.eclipse.jetty.server.session;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
/**
* NullSessionCacheFactory
*
* Factory for NullSessionCaches.
*/
-public class NullSessionCacheFactory implements SessionCacheFactory
+public class NullSessionCacheFactory extends AbstractSessionCacheFactory
{
- boolean _saveOnCreate;
- boolean _removeUnloadableSessions;
- NullSessionCache.WriteThroughMode _writeThroughMode;
-
- /**
- * @return the writeThroughMode
- */
- public NullSessionCache.WriteThroughMode getWriteThroughMode()
+ private static final Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+ @Override
+ public int getEvictionPolicy()
{
- return _writeThroughMode;
+ return SessionCache.EVICT_ON_SESSION_EXIT; //never actually stored
}
- /**
- * @param writeThroughMode the writeThroughMode to set
- */
- public void setWriteThroughMode(NullSessionCache.WriteThroughMode writeThroughMode)
+ @Override
+ public void setEvictionPolicy(int evictionPolicy)
{
- _writeThroughMode = writeThroughMode;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring eviction policy setting for NullSessionCaches");
}
- /**
- * @return the saveOnCreate
- */
- public boolean isSaveOnCreate()
+ @Override
+ public boolean isSaveOnInactiveEvict()
{
- return _saveOnCreate;
+ return false; //never kept in cache
}
- /**
- * @param saveOnCreate the saveOnCreate to set
- */
- public void setSaveOnCreate(boolean saveOnCreate)
+ @Override
+ public void setSaveOnInactiveEvict(boolean saveOnInactiveEvict)
{
- _saveOnCreate = saveOnCreate;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring eviction policy setting for NullSessionCaches");
}
- /**
- * @return the removeUnloadableSessions
- */
- public boolean isRemoveUnloadableSessions()
- {
- return _removeUnloadableSessions;
- }
-
- /**
- * @param removeUnloadableSessions the removeUnloadableSessions to set
- */
- public void setRemoveUnloadableSessions(boolean removeUnloadableSessions)
- {
- _removeUnloadableSessions = removeUnloadableSessions;
- }
-
- /**
- * @see org.eclipse.jetty.server.session.SessionCacheFactory#getSessionCache(org.eclipse.jetty.server.session.SessionHandler)
- */
@Override
public SessionCache getSessionCache(SessionHandler handler)
{
NullSessionCache cache = new NullSessionCache(handler);
cache.setSaveOnCreate(isSaveOnCreate());
cache.setRemoveUnloadableSessions(isRemoveUnloadableSessions());
- cache.setWriteThroughMode(_writeThroughMode);
+ cache.setFlushOnResponseCommit(isFlushOnResponseCommit());
return cache;
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java
index f33b4d83f1f..50656e17f40 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java
@@ -36,8 +36,7 @@ import javax.servlet.http.HttpSessionEvent;
import org.eclipse.jetty.io.CyclicTimeout;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Locker;
-import org.eclipse.jetty.util.thread.Locker.Lock;
+import org.eclipse.jetty.util.thread.AutoLock;
/**
* Session
@@ -73,15 +72,11 @@ public class Session implements SessionHandler.SessionIf
VALID, INVALID, INVALIDATING, CHANGING
}
- ;
-
public enum IdState
{
SET, CHANGING
}
- ;
-
protected final SessionData _sessionData; // the actual data associated with
// a session
@@ -98,7 +93,7 @@ public class Session implements SessionHandler.SessionIf
protected State _state = State.VALID; // state of the session:valid,invalid
// or being invalidated
- protected Locker _lock = new Locker(); // sync lock
+ protected AutoLock _lock = new AutoLock();
protected Condition _stateChangeCompleted = _lock.newCondition();
protected boolean _resident = false;
protected final SessionInactivityTimer _sessionInactivityTimer;
@@ -128,7 +123,7 @@ public class Session implements SessionHandler.SessionIf
long now = System.currentTimeMillis();
//handle what to do with the session after the timer expired
getSessionHandler().sessionInactivityTimerExpired(Session.this, now);
- try (Lock lock = Session.this.lock())
+ try (AutoLock lock = Session.this.lock())
{
//grab the lock and check what happened to the session: if it didn't get evicted and
//it hasn't expired, we need to reset the timer
@@ -213,7 +208,7 @@ public class Session implements SessionHandler.SessionIf
*/
public long getRequests()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _requests;
}
@@ -226,7 +221,7 @@ public class Session implements SessionHandler.SessionIf
protected void cookieSet()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
_sessionData.setCookieSet(_sessionData.getAccessed());
}
@@ -234,7 +229,7 @@ public class Session implements SessionHandler.SessionIf
protected void use()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
_requests++;
@@ -247,7 +242,7 @@ public class Session implements SessionHandler.SessionIf
protected boolean access(long time)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
if (!isValid() || !isResident())
return false;
@@ -267,7 +262,7 @@ public class Session implements SessionHandler.SessionIf
protected void complete()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
_requests--;
@@ -294,7 +289,7 @@ public class Session implements SessionHandler.SessionIf
*/
protected boolean isExpiredAt(long time)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _sessionData.isExpiredAt(time);
}
@@ -309,7 +304,7 @@ public class Session implements SessionHandler.SessionIf
protected boolean isIdleLongerThan(int sec)
{
long now = System.currentTimeMillis();
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return ((_sessionData.getAccessed() + (sec * 1000)) <= now);
}
@@ -350,7 +345,7 @@ public class Session implements SessionHandler.SessionIf
*/
public void unbindValue(java.lang.String name, Object value)
{
- if (value != null && value instanceof HttpSessionBindingListener)
+ if (value instanceof HttpSessionBindingListener)
((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this, name));
}
@@ -363,7 +358,7 @@ public class Session implements SessionHandler.SessionIf
*/
public void bindValue(java.lang.String name, Object value)
{
- if (value != null && value instanceof HttpSessionBindingListener)
+ if (value instanceof HttpSessionBindingListener)
((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this, name));
}
@@ -372,16 +367,31 @@ public class Session implements SessionHandler.SessionIf
*/
public void didActivate()
{
- HttpSessionEvent event = new HttpSessionEvent(this);
- for (Iterator iter = _sessionData.getKeys().iterator(); iter.hasNext();)
+ //A passivate listener might remove a non-serializable attribute that
+ //the activate listener might put back in again, which would spuriously
+ //set the dirty bit to true, causing another round of passivate/activate
+ //when the request exits. The store clears the dirty bit if it does a
+ //save, so ensure dirty flag is set to the value determined by the store,
+ //not a passivation listener.
+ boolean dirty = getSessionData().isDirty();
+
+ try
{
- Object value = _sessionData.getAttribute(iter.next());
- if (value instanceof HttpSessionActivationListener)
+ HttpSessionEvent event = new HttpSessionEvent(this);
+ for (String name : _sessionData.getKeys())
{
- HttpSessionActivationListener listener = (HttpSessionActivationListener)value;
- listener.sessionDidActivate(event);
+ Object value = _sessionData.getAttribute(name);
+ if (value instanceof HttpSessionActivationListener)
+ {
+ HttpSessionActivationListener listener = (HttpSessionActivationListener)value;
+ listener.sessionDidActivate(event);
+ }
}
}
+ finally
+ {
+ getSessionData().setDirty(dirty);
+ }
}
/**
@@ -390,9 +400,9 @@ public class Session implements SessionHandler.SessionIf
public void willPassivate()
{
HttpSessionEvent event = new HttpSessionEvent(this);
- for (Iterator iter = _sessionData.getKeys().iterator(); iter.hasNext();)
+ for (String name : _sessionData.getKeys())
{
- Object value = _sessionData.getAttribute(iter.next());
+ Object value = _sessionData.getAttribute(name);
if (value instanceof HttpSessionActivationListener)
{
HttpSessionActivationListener listener = (HttpSessionActivationListener)value;
@@ -403,7 +413,7 @@ public class Session implements SessionHandler.SessionIf
public boolean isValid()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.VALID;
}
@@ -411,21 +421,15 @@ public class Session implements SessionHandler.SessionIf
public boolean isInvalid()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.INVALID || _state == State.INVALIDATING;
}
}
- public boolean isChanging()
- {
- checkLocked();
- return _state == State.CHANGING;
- }
-
public long getCookieSetTime()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _sessionData.getCookieSet();
}
@@ -434,7 +438,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public long getCreationTime() throws IllegalStateException
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
return _sessionData.getCreated();
@@ -447,7 +451,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public String getId()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _sessionData.getId();
}
@@ -474,7 +478,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public long getLastAccessedTime()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
if (isInvalid())
{
@@ -501,10 +505,14 @@ public class Session implements SessionHandler.SessionIf
@Override
public void setMaxInactiveInterval(int secs)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
_sessionData.setMaxInactiveMs((long)secs * 1000L);
_sessionData.calcAndSetExpiry();
+ //dirty metadata writes can be skipped, but changing the
+ //maxinactiveinterval should write the session out because
+ //it may affect the session on other nodes, or on the same
+ //node in the case of the nullsessioncache
_sessionData.setDirty(true);
if (LOG.isDebugEnabled())
@@ -530,7 +538,7 @@ public class Session implements SessionHandler.SessionIf
{
long time = 0;
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
long remaining = _sessionData.getExpiry() - now;
long maxInactive = _sessionData.getMaxInactiveMs();
@@ -594,7 +602,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public int getMaxInactiveInterval()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
long maxInactiveMs = _sessionData.getMaxInactiveMs();
return (int)(maxInactiveMs < 0 ? -1 : maxInactiveMs / 1000);
@@ -624,8 +632,6 @@ public class Session implements SessionHandler.SessionIf
*/
protected void checkValidForWrite() throws IllegalStateException
{
- checkLocked();
-
if (_state == State.INVALID)
throw new IllegalStateException("Not valid for write: id=" + _sessionData.getId() +
" created=" + _sessionData.getCreated() +
@@ -649,8 +655,6 @@ public class Session implements SessionHandler.SessionIf
*/
protected void checkValidForRead() throws IllegalStateException
{
- checkLocked();
-
if (_state == State.INVALID)
throw new IllegalStateException("Invalid for read: id=" + _sessionData.getId() +
" created=" + _sessionData.getCreated() +
@@ -666,19 +670,13 @@ public class Session implements SessionHandler.SessionIf
throw new IllegalStateException("Invalid for read: id=" + _sessionData.getId() + " not resident");
}
- protected void checkLocked() throws IllegalStateException
- {
- if (!_lock.isLocked())
- throw new IllegalStateException("Session not locked");
- }
-
/**
* @see javax.servlet.http.HttpSession#getAttribute(java.lang.String)
*/
@Override
public Object getAttribute(String name)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
return _sessionData.getAttribute(name);
@@ -692,7 +690,7 @@ public class Session implements SessionHandler.SessionIf
@Deprecated(since = "Servlet API 2.2")
public Object getValue(String name)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
return _sessionData.getAttribute(name);
@@ -705,11 +703,11 @@ public class Session implements SessionHandler.SessionIf
@Override
public Enumeration getAttributeNames()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
final Iterator itor = _sessionData.getKeys().iterator();
- return new Enumeration()
+ return new Enumeration<>()
{
@Override
@@ -745,7 +743,7 @@ public class Session implements SessionHandler.SessionIf
@Deprecated(since = "Servlet API 2.2")
public String[] getValueNames() throws IllegalStateException
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
Iterator itor = _sessionData.getKeys().iterator();
@@ -768,7 +766,7 @@ public class Session implements SessionHandler.SessionIf
public void setAttribute(String name, Object value)
{
Object old = null;
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
// if session is not valid, don't accept the set
checkValidForWrite();
@@ -822,7 +820,7 @@ public class Session implements SessionHandler.SessionIf
String id = null;
String extendedId = null;
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
while (true)
{
@@ -858,7 +856,7 @@ public class Session implements SessionHandler.SessionIf
String newId = _handler._sessionIdManager.renewSessionId(id, extendedId, request);
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -936,7 +934,7 @@ public class Session implements SessionHandler.SessionIf
*
* @return the lock
*/
- public Lock lock()
+ public AutoLock lock()
{
return _lock.lock();
}
@@ -948,7 +946,7 @@ public class Session implements SessionHandler.SessionIf
{
boolean result = false;
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
while (true)
@@ -1007,7 +1005,7 @@ public class Session implements SessionHandler.SessionIf
*/
protected void finishInvalidate() throws IllegalStateException
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
try
{
@@ -1045,7 +1043,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public boolean isNew() throws IllegalStateException
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
checkValidForRead();
return _newSession;
@@ -1054,7 +1052,7 @@ public class Session implements SessionHandler.SessionIf
public void setIdChanged(boolean changed)
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
_idChanged = changed;
}
@@ -1062,7 +1060,7 @@ public class Session implements SessionHandler.SessionIf
public boolean isIdChanged()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return _idChanged;
}
@@ -1080,9 +1078,6 @@ public class Session implements SessionHandler.SessionIf
return _sessionData;
}
- /**
- *
- */
public void setResident(boolean resident)
{
_resident = resident;
@@ -1099,7 +1094,7 @@ public class Session implements SessionHandler.SessionIf
@Override
public String toString()
{
- try (Lock lock = _lock.lock())
+ try (AutoLock lock = _lock.lock())
{
return String.format("%s@%x{id=%s,x=%s,req=%d,res=%b}",
getClass().getSimpleName(),
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java
index 69bcceeafa1..b47810a9225 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java
@@ -126,11 +126,11 @@ public interface SessionCache extends LifeCycle
* @param id the session id
* @param session the current session object
* @throws Exception if any error occurred
- * @deprecated @see release
+ * @deprecated use {@link #release(String, Session)} instead
*/
+ @Deprecated
void put(String id, Session session) throws Exception;
-
-
+
/**
* Finish using a Session. This is called by the SessionHandler
* once a request is finished with a Session. SessionCache
@@ -142,7 +142,17 @@ public interface SessionCache extends LifeCycle
* @throws Exception if any error occurred
*/
void release(String id, Session session) throws Exception;
-
+
+ /**
+ * Called when a response is about to be committed. The
+ * cache can write the session to ensure that the
+ * SessionDataStore contains changes to the session
+ * that occurred during the lifetime of the request. This
+ * can help ensure that if a subsequent request goes to a
+ * different server, it will be able to see the session
+ * changes via the shared store.
+ */
+ void commit(Session session) throws Exception;
/**
* Check to see if a Session is in the cache. Does NOT consult
@@ -266,4 +276,18 @@ public interface SessionCache extends LifeCycle
* @return if true
unloadable session will be deleted
*/
boolean isRemoveUnloadableSessions();
+
+ /**
+ * If true, a dirty session will be written to the SessionDataStore
+ * just before a response is returned to the client. This ensures
+ * that subsequent requests to either the same node or a different
+ * node see the changed session data.
+ */
+ void setFlushOnResponseCommit(boolean flushOnResponse);
+
+ /**
+ * @return true
if dirty sessions should be written
+ * before the response is committed.
+ */
+ boolean isFlushOnResponseCommit();
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java
index 57a041c9ccc..3b3a9fd0493 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java
@@ -58,6 +58,7 @@ public class SessionData implements Serializable
protected Map _attributes;
protected boolean _dirty;
protected long _lastSaved; //time in msec since last save
+ protected boolean _metaDataDirty; //non-attribute data has changed
/**
* Serialize the attribute map of the session.
@@ -161,11 +162,6 @@ public class SessionData implements Serializable
}
public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs)
- {
- this(id, cpath, vhost, created, accessed, lastAccessed, maxInactiveMs, new ConcurrentHashMap());
- }
-
- public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs, Map attributes)
{
_id = id;
setContextPath(cpath);
@@ -175,7 +171,13 @@ public class SessionData implements Serializable
_lastAccessed = lastAccessed;
_maxInactiveMs = maxInactiveMs;
calcAndSetExpiry();
- _attributes = attributes;
+ _attributes = new ConcurrentHashMap<>();
+ }
+
+ public SessionData(String id, String cpath, String vhost, long created, long accessed, long lastAccessed, long maxInactiveMs, Map attributes)
+ {
+ this(id, cpath, vhost, created, accessed, lastAccessed, maxInactiveMs);
+ putAllAttributes(attributes);
}
/**
@@ -239,6 +241,22 @@ public class SessionData implements Serializable
setDirty(true);
}
+ /**
+ * @return the metaDataDirty
+ */
+ public boolean isMetaDataDirty()
+ {
+ return _metaDataDirty;
+ }
+
+ /**
+ * @param metaDataDirty true means non-attribute data has changed
+ */
+ public void setMetaDataDirty(boolean metaDataDirty)
+ {
+ _metaDataDirty = metaDataDirty;
+ }
+
/**
* @param name the name of the attribute
* @return the value of the attribute named
@@ -266,6 +284,15 @@ public class SessionData implements Serializable
return old;
}
+ /**
+ * Clear all dirty flags.
+ */
+ public void clean()
+ {
+ setDirty(false);
+ setMetaDataDirty(false);
+ }
+
public void putAllAttributes(Map attributes)
{
_attributes.putAll(attributes);
@@ -365,11 +392,13 @@ public class SessionData implements Serializable
public void calcAndSetExpiry(long time)
{
setExpiry(calcExpiry(time));
+ setMetaDataDirty(true);
}
public void calcAndSetExpiry()
{
setExpiry(calcExpiry());
+ setMetaDataDirty(true);
}
public long getCreated()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
index 4f4dc81d758..0505da7722c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
@@ -29,7 +29,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.SessionCookieConfig;
@@ -59,7 +58,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.util.statistic.SampleStatistic;
-import org.eclipse.jetty.util.thread.Locker.Lock;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -355,10 +354,9 @@ public class SessionHandler extends ScopedHandler
}
/**
- * Called by the {@link Request} when it finally finishes.
+ * Called when a request is finally leaving a session.
*
* @param session the session object
- * @see #access(HttpSession, boolean)
*/
public void complete(HttpSession session)
{
@@ -378,6 +376,28 @@ public class SessionHandler extends ScopedHandler
LOG.warn(e);
}
}
+
+ /**
+ * Called when a response is about to be committed.
+ * We might take this opportunity to persist the session
+ * so that any subsequent requests to other servers
+ * will see the modifications.
+ */
+ public void commit(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ Session s = ((SessionIf)session).getSession();
+ try
+ {
+ _sessionCache.commit(s);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
/*
* @see org.eclipse.thread.AbstractLifeCycle#doStart()
@@ -1264,7 +1284,7 @@ public class SessionHandler extends ScopedHandler
//1. valid
//2. expired
//3. idle
- try (Lock lock = session.lock())
+ try (AutoLock lock = session.lock())
{
if (session.getRequests() > 0)
return; //session can't expire or be idle if there is a request in it
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
index 6a0e4369f5a..f4a55a05179 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
@@ -56,11 +56,13 @@ import org.junit.jupiter.api.condition.DisabledOnOs;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -166,6 +168,66 @@ public class GracefulStopTest
client.close();
}
+
+ /**
+ * Test completed writes during shutdown do not close output
+ * @throws Exception on test failure
+ */
+ @Test
+ public void testWriteDuringShutdown() throws Exception
+ {
+ Server server = new Server();
+ server.setStopTimeout(1000);
+
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(0);
+ server.addConnector(connector);
+
+ ABHandler handler = new ABHandler();
+ StatisticsHandler stats = new StatisticsHandler();
+ server.setHandler(stats);
+ stats.setHandler(handler);
+
+ server.start();
+
+ Thread stopper = new Thread(() ->
+ {
+ try
+ {
+ handler.latchA.await();
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ });
+ stopper.start();
+
+ final int port = connector.getLocalPort();
+ try(Socket client = new Socket("127.0.0.1", port))
+ {
+ client.getOutputStream().write((
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost:" + port + "\r\n" +
+ "\r\n"
+ ).getBytes());
+ client.getOutputStream().flush();
+
+ while (!connector.isShutdown())
+ Thread.sleep(10);
+
+ handler.latchB.countDown();
+
+ String response = IO.toString(client.getInputStream());
+ assertThat(response, startsWith("HTTP/1.1 200 "));
+ assertThat(response, containsString("Content-Length: 2"));
+ assertThat(response, containsString("Connection: close"));
+ assertThat(response, endsWith("ab"));
+ }
+ stopper.join();
+ }
+
/**
* Test of standard graceful timeout mechanism when a block request does
* complete. Note that even though the request completes after 100ms, the
@@ -736,6 +798,30 @@ public class GracefulStopTest
}
}
+ static class ABHandler extends AbstractHandler
+ {
+ final CountDownLatch latchA = new CountDownLatch(1);
+ final CountDownLatch latchB = new CountDownLatch(1);
+
+ @Override
+ public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ response.setContentLength(2);
+ response.getOutputStream().write("a".getBytes());
+ try
+ {
+ latchA.countDown();
+ latchB.await();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ response.flushBuffer();
+ response.getOutputStream().write("b".getBytes());
+ }
+ }
+
static class TestHandler extends AbstractHandler
{
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
index b0f753b279e..a3ecb471d4d 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
@@ -651,12 +651,6 @@ public class HttpOutputTest
_next.write(BufferUtil.toBuffer(s), complete, callback);
}
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return _next.isOptimizedForDirectBuffers();
- }
-
@Override
public Interceptor getNextInterceptor()
{
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index afb088c10c1..4322302b8f0 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -174,12 +174,6 @@ public class ResponseTest
{
_channelError = failure;
}
-
- @Override
- public boolean isOptimizedForDirectBuffers()
- {
- return false;
- }
});
}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
index ddcaa278423..5c688222192 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
@@ -43,11 +43,11 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl
{
private static final Logger LOG = Log.getLogger(BaseHolder.class);
- protected final Source _source;
- protected transient Class extends T> _class;
- protected String _className;
- protected boolean _extInstance;
- protected ServletHandler _servletHandler;
+ private final Source _source;
+ private Class extends T> _class;
+ private String _className;
+ private T _instance;
+ private ServletHandler _servletHandler;
protected BaseHolder(Source source)
{
@@ -101,7 +101,7 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl
public void doStop()
throws Exception
{
- if (!_extInstance)
+ if (_instance == null)
_class = null;
}
@@ -163,12 +163,26 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl
}
}
+ protected synchronized void setInstance(T instance)
+ {
+ _instance = instance;
+ if (instance == null)
+ setHeldClass(null);
+ else
+ setHeldClass((Class)instance.getClass());
+ }
+
+ protected synchronized T getInstance()
+ {
+ return _instance;
+ }
+
/**
* @return True if this holder was created for a specific instance.
*/
- public boolean isInstance()
+ public synchronized boolean isInstance()
{
- return _extInstance;
+ return _instance != null;
}
@Override
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
index 3881bb05ff3..9b0f0b6261a 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
@@ -91,11 +91,10 @@ public class FilterHolder extends Holder
{
super.doStart();
- if (!javax.servlet.Filter.class
- .isAssignableFrom(_class))
+ if (!javax.servlet.Filter.class.isAssignableFrom(getHeldClass()))
{
- String msg = _class + " is not a javax.servlet.Filter";
- super.stop();
+ String msg = getHeldClass() + " is not a javax.servlet.Filter";
+ doStop();
throw new IllegalStateException(msg);
}
}
@@ -103,15 +102,18 @@ public class FilterHolder extends Holder
@Override
public void initialize() throws Exception
{
- if (!_initialized)
+ synchronized (this)
{
- super.initialize();
+ if (_filter != null)
+ return;
+ super.initialize();
+ _filter = getInstance();
if (_filter == null)
{
try
{
- ServletContext context = _servletHandler.getServletContext();
+ ServletContext context = getServletHandler().getServletContext();
_filter = (context instanceof ServletContextHandler.Context)
? context.createFilter(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
@@ -126,37 +128,30 @@ public class FilterHolder extends Holder
throw ex;
}
}
-
_config = new Config();
if (LOG.isDebugEnabled())
LOG.debug("Filter.init {}", _filter);
_filter.init(_config);
}
-
- _initialized = true;
}
@Override
public void doStop()
throws Exception
{
+ super.doStop();
+ _config = null;
if (_filter != null)
{
try
{
destroyInstance(_filter);
}
- catch (Exception e)
+ finally
{
- LOG.warn(e);
+ _filter = null;
}
}
- if (!_extInstance)
- _filter = null;
-
- _config = null;
- _initialized = false;
- super.doStop();
}
@Override
@@ -172,11 +167,7 @@ public class FilterHolder extends Holder
public synchronized void setFilter(Filter filter)
{
- _filter = filter;
- _extInstance = true;
- setHeldClass(filter.getClass());
- if (getName() == null)
- setName(filter.getClass().getName());
+ setInstance(filter);
}
public Filter getFilter()
@@ -187,19 +178,19 @@ public class FilterHolder extends Holder
@Override
public void dump(Appendable out, String indent) throws IOException
{
- if (_initParams.isEmpty())
+ if (getInitParameters().isEmpty())
Dumpable.dumpObjects(out, indent, this,
_filter == null ? getHeldClass() : _filter);
else
Dumpable.dumpObjects(out, indent, this,
_filter == null ? getHeldClass() : _filter,
- new DumpableCollection("initParams", _initParams.entrySet()));
+ new DumpableCollection("initParams", getInitParameters().entrySet()));
}
@Override
public String toString()
{
- return String.format("%s@%x==%s,inst=%b,async=%b", _name, hashCode(), _className, _filter != null, isAsyncSupported());
+ return String.format("%s@%x==%s,inst=%b,async=%b", getName(), hashCode(), getClassName(), _filter != null, isAsyncSupported());
}
public FilterRegistration.Dynamic getRegistration()
@@ -220,9 +211,9 @@ public class FilterHolder extends Holder
mapping.setServletNames(servletNames);
mapping.setDispatcherTypes(dispatcherTypes);
if (isMatchAfter)
- _servletHandler.addFilterMapping(mapping);
+ getServletHandler().addFilterMapping(mapping);
else
- _servletHandler.prependFilterMapping(mapping);
+ getServletHandler().prependFilterMapping(mapping);
}
@Override
@@ -234,15 +225,15 @@ public class FilterHolder extends Holder
mapping.setPathSpecs(urlPatterns);
mapping.setDispatcherTypes(dispatcherTypes);
if (isMatchAfter)
- _servletHandler.addFilterMapping(mapping);
+ getServletHandler().addFilterMapping(mapping);
else
- _servletHandler.prependFilterMapping(mapping);
+ getServletHandler().prependFilterMapping(mapping);
}
@Override
public Collection getServletNameMappings()
{
- FilterMapping[] mappings = _servletHandler.getFilterMappings();
+ FilterMapping[] mappings = getServletHandler().getFilterMappings();
List names = new ArrayList();
for (FilterMapping mapping : mappings)
{
@@ -258,7 +249,7 @@ public class FilterHolder extends Holder
@Override
public Collection getUrlPatternMappings()
{
- FilterMapping[] mappings = _servletHandler.getFilterMappings();
+ FilterMapping[] mappings = getServletHandler().getFilterMappings();
List patterns = new ArrayList();
for (FilterMapping mapping : mappings)
{
@@ -277,7 +268,7 @@ public class FilterHolder extends Holder
@Override
public String getFilterName()
{
- return _name;
+ return getName();
}
}
}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
index 50e9e0c3dfe..adcb14bbdae 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
@@ -45,16 +45,15 @@ public abstract class Holder extends BaseHolder
{
private static final Logger LOG = Log.getLogger(Holder.class);
- protected final Map _initParams = new HashMap(3);
- protected String _displayName;
- protected boolean _asyncSupported;
- protected String _name;
- protected boolean _initialized = false;
+ private final Map _initParams = new HashMap(3);
+ private String _displayName;
+ private boolean _asyncSupported;
+ private String _name;
protected Holder(Source source)
{
super(source);
- switch (_source.getOrigin())
+ switch (getSource().getOrigin())
{
case JAVAX_API:
case DESCRIPTOR:
@@ -98,6 +97,14 @@ public abstract class Holder extends BaseHolder
return _name;
}
+ @Override
+ protected synchronized void setInstance(T instance)
+ {
+ super.setInstance(instance);
+ if (getName() == null)
+ setName(String.format("%s@%x", instance.getClass().getName(), instance.hashCode()));
+ }
+
public void destroyInstance(Object instance)
throws Exception
{
@@ -175,7 +182,7 @@ public abstract class Holder extends BaseHolder
@Override
public String toString()
{
- return String.format("%s@%x==%s", _name, hashCode(), _className);
+ return String.format("%s@%x==%s", _name, hashCode(), getClassName());
}
protected class HolderConfig
@@ -183,7 +190,7 @@ public abstract class Holder extends BaseHolder
public ServletContext getServletContext()
{
- return _servletHandler.getServletContext();
+ return getServletHandler().getServletContext();
}
public String getInitParameter(String param)
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java
index 245101dd63b..9313237ea4e 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java
@@ -64,52 +64,66 @@ public class ListenerHolder extends BaseHolder
*/
public void setListener(EventListener listener)
{
- _listener = listener;
- _extInstance = true;
- setHeldClass(_listener.getClass());
+ setInstance(listener);
}
@Override
public void doStart() throws Exception
{
super.doStart();
- if (!java.util.EventListener.class.isAssignableFrom(_class))
+ if (!java.util.EventListener.class.isAssignableFrom(getHeldClass()))
{
- String msg = _class + " is not a java.util.EventListener";
+ String msg = getHeldClass() + " is not a java.util.EventListener";
super.stop();
throw new IllegalStateException(msg);
}
-
+
ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler();
- if (_listener == null)
+ if (contextHandler != null)
{
- //create an instance of the listener and decorate it
- try
+ _listener = getInstance();
+ if (_listener == null)
{
- ServletContext scontext = contextHandler.getServletContext();
- _listener = (scontext instanceof ServletContextHandler.Context)
- ? scontext.createListener(getHeldClass())
- : getHeldClass().getDeclaredConstructor().newInstance();
- }
- catch (ServletException ex)
- {
- Throwable cause = ex.getRootCause();
- if (cause instanceof InstantiationException)
- throw (InstantiationException)cause;
- if (cause instanceof IllegalAccessException)
- throw (IllegalAccessException)cause;
- throw ex;
+ //create an instance of the listener and decorate it
+ try
+ {
+ ServletContext scontext = contextHandler.getServletContext();
+ _listener = (scontext instanceof ServletContextHandler.Context)
+ ? scontext.createListener(getHeldClass())
+ : getHeldClass().getDeclaredConstructor().newInstance();
+ }
+ catch (ServletException ex)
+ {
+ Throwable cause = ex.getRootCause();
+ if (cause instanceof InstantiationException)
+ throw (InstantiationException)cause;
+ if (cause instanceof IllegalAccessException)
+ throw (IllegalAccessException)cause;
+ throw ex;
+ }
}
+ contextHandler.addEventListener(_listener);
}
- contextHandler.addEventListener(_listener);
}
@Override
public void doStop() throws Exception
{
super.doStop();
- if (!_extInstance)
- _listener = null;
+ if (_listener != null)
+ {
+ try
+ {
+ ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler();
+ if (contextHandler != null)
+ contextHandler.removeEventListener(_listener);
+ getServletHandler().destroyListener(_listener);
+ }
+ finally
+ {
+ _listener = null;
+ }
+ }
}
@Override
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
index 3ec2bd383f9..9f69fce9b3d 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
@@ -121,8 +121,6 @@ public class ServletContextHandler extends ContextHandler
public interface ServletContainerInitializerCaller extends LifeCycle {}
- ;
-
protected final DecoratedObjectFactory _objFactory;
protected Class extends SecurityHandler> _defaultSecurityHandlerClass = org.eclipse.jetty.security.ConstraintSecurityHandler.class;
protected SessionHandler _sessionHandler;
@@ -731,6 +729,11 @@ public class ServletContextHandler extends ContextHandler
_objFactory.destroy(filter);
}
+ void destroyListener(EventListener listener)
+ {
+ _objFactory.destroy(listener);
+ }
+
public static class JspPropertyGroup implements JspPropertyGroupDescriptor
{
private List _urlPatterns = new ArrayList();
@@ -1274,6 +1277,11 @@ public class ServletContextHandler extends ContextHandler
}
}
+ public void destroyFilter(T f)
+ {
+ _objFactory.destroy(f);
+ }
+
@Override
public T createServlet(Class c) throws ServletException
{
@@ -1289,6 +1297,11 @@ public class ServletContextHandler extends ContextHandler
}
}
+ public void destroyServlet(T s)
+ {
+ _objFactory.destroy(s);
+ }
+
@Override
public Set getDefaultSessionTrackingModes()
{
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index 31e6f940d96..8bae9ebae07 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -1706,6 +1707,12 @@ public class ServletHandler extends ScopedHandler
_contextHandler.destroyFilter(filter);
}
+ void destroyListener(EventListener listener)
+ {
+ if (_contextHandler != null)
+ _contextHandler.destroyListener(listener);
+ }
+
@SuppressWarnings("serial")
public static class Default404Servlet extends HttpServlet
{
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index e3435fdc511..1466a0d9f48 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -32,6 +32,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.GenericServlet;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
@@ -43,6 +45,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.ServletSecurityElement;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.RunAsToken;
@@ -70,7 +73,6 @@ import org.eclipse.jetty.util.log.Logger;
@ManagedObject("Servlet Holder")
public class ServletHolder extends Holder implements UserIdentity.Scope, Comparable
{
-
private static final Logger LOG = Log.getLogger(ServletHolder.class);
private int _initOrder = -1;
private boolean _initOnStartup = false;
@@ -82,11 +84,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
private ServletRegistration.Dynamic _registration;
private JspContainer _jspContainer;
- private Servlet _servlet;
- private long _unavailable;
+ private volatile Servlet _servlet;
private Config _config;
private boolean _enabled = true;
- private UnavailableException _unavailableEx;
public static final String APACHE_SENTINEL_CLASS = "org.apache.tomcat.InstanceManager";
public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
@@ -167,7 +167,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
*/
public UnavailableException getUnavailableException()
{
- return _unavailableEx;
+ Servlet servlet = _servlet;
+ if (servlet instanceof UnavailableServlet)
+ return ((UnavailableServlet)servlet).getUnavailableException();
+ return null;
}
public synchronized void setServlet(Servlet servlet)
@@ -175,11 +178,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (servlet == null || servlet instanceof SingleThreadModel)
throw new IllegalArgumentException(SingleThreadModel.class.getName() + " has been deprecated since Servlet API 2.4");
- _extInstance = true;
- _servlet = servlet;
- setHeldClass(servlet.getClass());
- if (getName() == null)
- setName(servlet.getClass().getName() + "-" + super.hashCode());
+ setInstance(servlet);
}
@ManagedAttribute(value = "initialization order", readonly = true)
@@ -218,20 +217,20 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (sh._initOrder > _initOrder)
return -1;
- // consider _className, need to position properly when one is configured but not the other
+ // consider getClassName(), need to position properly when one is configured but not the other
int c;
- if (_className == null && sh._className == null)
+ if (getClassName() == null && sh.getClassName() == null)
c = 0;
- else if (_className == null)
+ else if (getClassName() == null)
c = -1;
- else if (sh._className == null)
+ else if (sh.getClassName() == null)
c = 1;
else
- c = _className.compareTo(sh._className);
+ c = getClassName().compareTo(sh.getClassName());
- // if _initOrder and _className are the same, consider the _name
+ // if _initOrder and getClassName() are the same, consider the getName()
if (c == 0)
- c = _name.compareTo(sh._name);
+ c = getName().compareTo(sh.getName());
return c;
}
@@ -245,7 +244,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
@Override
public int hashCode()
{
- return _name == null ? System.identityHashCode(this) : _name.hashCode();
+ return getName() == null ? System.identityHashCode(this) : getName().hashCode();
}
/**
@@ -309,7 +308,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
public void doStart()
throws Exception
{
- _unavailable = 0;
if (!_enabled)
return;
@@ -342,7 +340,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
//copy jsp init params that don't exist for this servlet
for (Map.Entry entry : jsp.getInitParameters().entrySet())
{
- if (!_initParams.containsKey(entry.getKey()))
+ if (!getInitParameters().containsKey(entry.getKey()))
setInitParameter(entry.getKey(), entry.getValue());
}
//jsp specific: set up the jsp-file on the JspServlet. If load-on-startup is >=0 and the jsp container supports
@@ -365,7 +363,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
catch (UnavailableException ex)
{
makeUnavailable(ex);
- if (_servletHandler.isStartWithUnavailable())
+ if (getServletHandler().isStartWithUnavailable())
{
LOG.ignore(ex);
return;
@@ -382,7 +380,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
catch (UnavailableException ex)
{
makeUnavailable(ex);
- if (_servletHandler.isStartWithUnavailable())
+ if (getServletHandler().isStartWithUnavailable())
{
LOG.ignore(ex);
return;
@@ -394,16 +392,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
//check if we need to forcibly set load-on-startup
checkInitOnStartup();
- _identityService = _servletHandler.getIdentityService();
- if (_identityService != null && _runAsRole != null)
- _runAsToken = _identityService.newRunAsToken(_runAsRole);
+ if (_runAsRole == null)
+ {
+ _identityService = null;
+ _runAsToken = null;
+ }
+ else
+ {
+ _identityService = getServletHandler().getIdentityService();
+ if (_identityService != null)
+ _runAsToken = _identityService.newRunAsToken(_runAsRole);
+ }
_config = new Config();
synchronized (this)
{
- // TODO: remove support for deprecated SingleThreadModel??
- if (_class != null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
+ if (getHeldClass() != null && javax.servlet.SingleThreadModel.class.isAssignableFrom(getHeldClass()))
_servlet = new SingleThreadedWrapper();
}
}
@@ -412,57 +417,37 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
public void initialize()
throws Exception
{
- if (!_initialized)
+ synchronized (this)
{
- super.initialize();
- if (_extInstance || _initOnStartup)
+ if (_servlet == null && (_initOnStartup || isInstance()))
{
- try
- {
- initServlet();
- }
- catch (Exception e)
- {
- if (_servletHandler.isStartWithUnavailable())
- LOG.ignore(e);
- else
- throw e;
- }
+ super.initialize();
+ initServlet();
}
}
- _initialized = true;
}
@Override
public void doStop()
throws Exception
{
- Object oldRunAs = null;
- if (_servlet != null)
+ synchronized (this)
{
- try
+ Servlet servlet = _servlet;
+ if (servlet != null)
{
- if (_identityService != null)
- oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
-
- destroyInstance(_servlet);
- }
- catch (Exception e)
- {
- LOG.warn(e);
- }
- finally
- {
- if (_identityService != null)
- _identityService.unsetRunAs(oldRunAs);
+ _servlet = null;
+ try
+ {
+ destroyInstance(servlet);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
}
+ _config = null;
}
-
- if (!_extInstance)
- _servlet = null;
-
- _config = null;
- _initialized = false;
}
@Override
@@ -482,41 +467,24 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
* @return The servlet
* @throws ServletException if unable to init the servlet on first use
*/
- public synchronized Servlet getServlet()
+ public Servlet getServlet()
throws ServletException
{
Servlet servlet = _servlet;
- if (servlet != null && _unavailable == 0)
- return servlet;
-
- synchronized (this)
+ if (servlet == null)
{
- // Handle previous unavailability
- if (_unavailable != 0)
+ synchronized (this)
{
- if (_unavailable < 0 || _unavailable > 0 && System.currentTimeMillis() < _unavailable)
- throw _unavailableEx;
- _unavailable = 0;
- _unavailableEx = null;
- }
-
- servlet = _servlet;
- if (servlet != null)
- return servlet;
-
- if (isRunning())
- {
- if (_class == null)
- throw new UnavailableException("Servlet Not Initialized");
- if (_unavailable != 0 || !_initOnStartup)
- initServlet();
servlet = _servlet;
- if (servlet == null)
- throw new UnavailableException("Could not instantiate " + _class);
+ if (servlet == null && isRunning())
+ {
+ if (getHeldClass() != null)
+ initServlet();
+ servlet = _servlet;
+ }
}
-
- return servlet;
}
+ return servlet;
}
/**
@@ -526,13 +494,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
*/
public Servlet getServletInstance()
{
- Servlet servlet = _servlet;
- if (servlet != null)
- return servlet;
- synchronized (this)
- {
- return _servlet;
- }
+ return _servlet;
}
/**
@@ -543,9 +505,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
public void checkServletType()
throws UnavailableException
{
- if (_class == null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
+ if (getHeldClass() == null || !javax.servlet.Servlet.class.isAssignableFrom(getHeldClass()))
{
- throw new UnavailableException("Servlet " + _class + " is not a javax.servlet.Servlet");
+ throw new UnavailableException("Servlet " + getHeldClass() + " is not a javax.servlet.Servlet");
}
}
@@ -554,18 +516,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
*/
public boolean isAvailable()
{
- if (isStarted() && _unavailable == 0)
- return true;
- try
- {
- getServlet();
- }
- catch (Exception e)
- {
- LOG.ignore(e);
- }
-
- return isStarted() && _unavailable == 0;
+ return (isStarted() && !(_servlet instanceof UnavailableServlet));
}
/**
@@ -576,30 +527,19 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
*/
private void checkInitOnStartup()
{
- if (_class == null)
+ if (getHeldClass() == null)
return;
- if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
+ if ((getHeldClass().getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
setInitOrder(Integer.MAX_VALUE);
}
- private void makeUnavailable(UnavailableException e)
+ private Servlet makeUnavailable(UnavailableException e)
{
- if (_unavailableEx == e && _unavailable != 0)
- return;
-
- _servletHandler.getServletContext().log("unavailable", e);
-
- _unavailableEx = e;
- _unavailable = -1;
- if (e.isPermanent())
- _unavailable = -1;
- else
+ synchronized (this)
{
- if (_unavailableEx.getUnavailableSeconds() > 0)
- _unavailable = System.currentTimeMillis() + 1000 * _unavailableEx.getUnavailableSeconds();
- else
- _unavailable = System.currentTimeMillis() + 5000; // TODO configure
+ _servlet = new UnavailableServlet(e, _servlet);
+ return _servlet;
}
}
@@ -609,37 +549,39 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
makeUnavailable((UnavailableException)e);
else
{
- ServletContext ctx = _servletHandler.getServletContext();
+ ServletContext ctx = getServletHandler().getServletContext();
if (ctx == null)
LOG.info("unavailable", e);
else
ctx.log("unavailable", e);
- _unavailableEx = new UnavailableException(String.valueOf(e), -1)
+ UnavailableException unavailable = new UnavailableException(String.valueOf(e), -1)
{
{
initCause(e);
}
};
- _unavailable = -1;
+ makeUnavailable(unavailable);
}
}
private synchronized void initServlet()
throws ServletException
{
- Object oldRunAs = null;
try
{
+ if (_servlet == null)
+ _servlet = getInstance();
if (_servlet == null)
_servlet = newInstance();
if (_config == null)
_config = new Config();
// Handle run as
- if (_identityService != null)
- {
- oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
- }
+ if (_identityService != null && _runAsToken != null)
+ _servlet = new RunAsServlet(_servlet, _identityService, _runAsToken);
+
+ if (!isAsyncSupported())
+ _servlet = new NotAsyncServlet(_servlet);
// Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
if (isJspServlet())
@@ -659,30 +601,21 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
catch (UnavailableException e)
{
makeUnavailable(e);
- _servlet = null;
- _config = null;
- throw e;
+ if (getServletHandler().isStartWithUnavailable())
+ LOG.warn(e);
+ else
+ throw e;
}
catch (ServletException e)
{
makeUnavailable(e.getCause() == null ? e : e.getCause());
- _servlet = null;
- _config = null;
throw e;
}
catch (Exception e)
{
makeUnavailable(e);
- _servlet = null;
- _config = null;
throw new ServletException(this.toString(), e);
}
- finally
- {
- // pop run-as role
- if (_identityService != null)
- _identityService.unsetRunAs(oldRunAs);
- }
}
/**
@@ -693,7 +626,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
/* Set the webapp's classpath for Jasper */
- ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+ ch.setAttribute("org.apache.catalina.jspgetHeldClass()path", ch.getClassPath());
/* Set up other classpath attribute */
if ("?".equals(getInitParameter("classpath")))
@@ -812,56 +745,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
UnavailableException,
IOException
{
- if (_class == null)
- throw new UnavailableException("Servlet Not Initialized");
-
- Servlet servlet = getServlet();
-
- // Service the request
- Object oldRunAs = null;
- boolean suspendable = baseRequest.isAsyncSupported();
try
{
- // Handle aliased path
- if (_forcedPath != null)
- adaptForcedPathToJspContainer(request);
-
- // Handle run as
- if (_identityService != null)
- oldRunAs = _identityService.setRunAs(baseRequest.getResolvedUserIdentity(), _runAsToken);
-
- if (baseRequest.isAsyncSupported() && !isAsyncSupported())
- {
- try
- {
- baseRequest.setAsyncSupported(false, this.toString());
- servlet.service(request, response);
- }
- finally
- {
- baseRequest.setAsyncSupported(true, null);
- }
- }
- else
- servlet.service(request, response);
+ Servlet servlet = getServlet();
+ if (servlet == null)
+ throw new UnavailableException("Servlet Not Initialized");
+ servlet.service(request, response);
}
catch (UnavailableException e)
{
- makeUnavailable(e);
- throw _unavailableEx;
- }
- finally
- {
- // Pop run-as role.
- if (_identityService != null)
- _identityService.unsetRunAs(oldRunAs);
+ makeUnavailable(e).service(request, response);
}
}
protected boolean isJspServlet()
{
Servlet servlet = getServletInstance();
- Class> c = servlet == null ? _class : servlet.getClass();
+ Class> c = servlet == null ? getHeldClass() : servlet.getClass();
while (c != null)
{
@@ -879,11 +779,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
return ("org.apache.jasper.servlet.JspServlet".equals(classname));
}
- private void adaptForcedPathToJspContainer(ServletRequest request)
- {
- //no-op for apache jsp
- }
-
private void detectJspContainer()
{
if (_jspContainer == null)
@@ -1051,7 +946,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
Set clash = null;
for (String pattern : urlPatterns)
{
- ServletMapping mapping = _servletHandler.getServletMapping(pattern);
+ ServletMapping mapping = getServletHandler().getServletMapping(pattern);
if (mapping != null)
{
//if the servlet mapping was from a default descriptor, then allow it to be overridden
@@ -1072,7 +967,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
ServletMapping mapping = new ServletMapping(Source.JAVAX_API);
mapping.setServletName(ServletHolder.this.getName());
mapping.setPathSpecs(urlPatterns);
- _servletHandler.addServletMapping(mapping);
+ getServletHandler().addServletMapping(mapping);
return Collections.emptySet();
}
@@ -1080,7 +975,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
@Override
public Collection getMappings()
{
- ServletMapping[] mappings = _servletHandler.getServletMappings();
+ ServletMapping[] mappings = getServletHandler().getServletMappings();
List patterns = new ArrayList();
if (mappings != null)
{
@@ -1134,7 +1029,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
@Override
public Set setServletSecurity(ServletSecurityElement securityElement)
{
- return _servletHandler.setServletSecurity(this, securityElement);
+ return getServletHandler().setServletSecurity(this, securityElement);
}
}
@@ -1259,9 +1154,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
try
{
ServletContext ctx = getServletHandler().getServletContext();
- if (ctx == null)
- return getHeldClass().getDeclaredConstructor().newInstance();
- return ctx.createServlet(getHeldClass());
+ if (ctx instanceof ServletContextHandler.Context)
+ return ctx.createServlet(getHeldClass());
+ return getHeldClass().getDeclaredConstructor().newInstance();
+
}
catch (ServletException ex)
{
@@ -1281,18 +1177,221 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
@Override
public void dump(Appendable out, String indent) throws IOException
{
- if (_initParams.isEmpty())
+ if (getInitParameters().isEmpty())
Dumpable.dumpObjects(out, indent, this,
_servlet == null ? getHeldClass() : _servlet);
else
Dumpable.dumpObjects(out, indent, this,
_servlet == null ? getHeldClass() : _servlet,
- new DumpableCollection("initParams", _initParams.entrySet()));
+ new DumpableCollection("initParams", getInitParameters().entrySet()));
}
@Override
public String toString()
{
- return String.format("%s@%x==%s,jsp=%s,order=%d,inst=%b,async=%b", _name, hashCode(), _className, _forcedPath, _initOrder, _servlet != null, isAsyncSupported());
+ return String.format("%s@%x==%s,jsp=%s,order=%d,inst=%b,async=%b", getName(), hashCode(), getClassName(), _forcedPath, _initOrder, _servlet != null, isAsyncSupported());
+ }
+
+ private class UnavailableServlet extends GenericServlet
+ {
+ final UnavailableException _unavailableException;
+ final Servlet _servlet;
+ final long _available;
+
+ public UnavailableServlet(UnavailableException unavailableException, Servlet servlet)
+ {
+ _unavailableException = unavailableException;
+
+ if (unavailableException.isPermanent())
+ {
+ _servlet = null;
+ _available = -1;
+ if (servlet != null)
+ {
+ try
+ {
+ destroyInstance(servlet);
+ }
+ catch (Throwable th)
+ {
+ if (th != unavailableException)
+ unavailableException.addSuppressed(th);
+ }
+ }
+ }
+ else
+ {
+ _servlet = servlet;
+ _available = System.nanoTime() + TimeUnit.SECONDS.toNanos(unavailableException.getUnavailableSeconds());
+ }
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ if (_available == -1)
+ ((HttpServletResponse)res).sendError(HttpServletResponse.SC_NOT_FOUND);
+ else if (System.nanoTime() < _available)
+ ((HttpServletResponse)res).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ else
+ {
+ synchronized (ServletHolder.this)
+ {
+ ServletHolder.this._servlet = this._servlet;
+ _servlet.service(req, res);
+ }
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ if (_servlet != null)
+ {
+ try
+ {
+ destroyInstance(_servlet);
+ }
+ catch (Throwable th)
+ {
+ LOG.warn(th);
+ }
+ }
+ }
+
+ public UnavailableException getUnavailableException()
+ {
+ return _unavailableException;
+ }
+ }
+
+ private static class WrapperServlet implements Servlet
+ {
+ final Servlet _servlet;
+
+ public WrapperServlet(Servlet servlet)
+ {
+ _servlet = servlet;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ _servlet.init(config);
+ }
+
+ @Override
+ public ServletConfig getServletConfig()
+ {
+ return _servlet.getServletConfig();
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ _servlet.service(req, res);
+ }
+
+ @Override
+ public String getServletInfo()
+ {
+ return _servlet.getServletInfo();
+ }
+
+ @Override
+ public void destroy()
+ {
+ _servlet.destroy();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s:%s", this.getClass().getSimpleName(), _servlet.toString());
+ }
+ }
+
+ private static class RunAsServlet extends WrapperServlet
+ {
+ final IdentityService _identityService;
+ final RunAsToken _runAsToken;
+
+ public RunAsServlet(Servlet servlet, IdentityService identityService, RunAsToken runAsToken)
+ {
+ super(servlet);
+ _identityService = identityService;
+ _runAsToken = runAsToken;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
+ try
+ {
+ _servlet.init(config);
+ }
+ finally
+ {
+ _identityService.unsetRunAs(oldRunAs);
+ }
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
+ try
+ {
+ _servlet.service(req, res);
+ }
+ finally
+ {
+ _identityService.unsetRunAs(oldRunAs);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
+ try
+ {
+ _servlet.destroy();
+ }
+ finally
+ {
+ _identityService.unsetRunAs(oldRunAs);
+ }
+ }
+ }
+
+ private static class NotAsyncServlet extends WrapperServlet
+ {
+ public NotAsyncServlet(Servlet servlet)
+ {
+ super(servlet);
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ if (req.isAsyncSupported())
+ {
+ try
+ {
+ ((Request)req).setAsyncSupported(false, this.toString());
+ _servlet.service(req, res);
+ }
+ finally
+ {
+ ((Request)req).setAsyncSupported(true, null);
+ }
+ }
+ else
+ {
+ _servlet.service(req, res);
+ }
+ }
}
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
index a82e7a63452..4f7ea9af797 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
@@ -36,6 +37,7 @@ import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -58,6 +60,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ErrorPageTest
@@ -67,6 +70,8 @@ public class ErrorPageTest
private StacklessLogging _stackless;
private static CountDownLatch __asyncSendErrorCompleted;
private ErrorPageErrorHandler _errorPageErrorHandler;
+ private static AtomicBoolean __destroyed;
+ private ServletContextHandler _context;
@BeforeEach
public void init() throws Exception
@@ -75,25 +80,24 @@ public class ErrorPageTest
_connector = new LocalConnector(_server);
_server.addConnector(_connector);
- ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
+ _context = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
- _server.setHandler(context);
+ _server.setHandler(_context);
- context.setContextPath("/");
-
- context.addFilter(SingleDispatchFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
-
- context.addServlet(DefaultServlet.class, "/");
- context.addServlet(FailServlet.class, "/fail/*");
- context.addServlet(FailClosedServlet.class, "/fail-closed/*");
- context.addServlet(ErrorServlet.class, "/error/*");
- context.addServlet(AppServlet.class, "/app/*");
- context.addServlet(LongerAppServlet.class, "/longer.app/*");
- context.addServlet(SyncSendErrorServlet.class, "/sync/*");
- context.addServlet(AsyncSendErrorServlet.class, "/async/*");
- context.addServlet(NotEnoughServlet.class, "/notenough/*");
- context.addServlet(DeleteServlet.class, "/delete/*");
- context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*");
+ _context.setContextPath("/");
+ _context.addFilter(SingleDispatchFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+ _context.addServlet(DefaultServlet.class, "/");
+ _context.addServlet(FailServlet.class, "/fail/*");
+ _context.addServlet(FailClosedServlet.class, "/fail-closed/*");
+ _context.addServlet(ErrorServlet.class, "/error/*");
+ _context.addServlet(AppServlet.class, "/app/*");
+ _context.addServlet(LongerAppServlet.class, "/longer.app/*");
+ _context.addServlet(SyncSendErrorServlet.class, "/sync/*");
+ _context.addServlet(AsyncSendErrorServlet.class, "/async/*");
+ _context.addServlet(NotEnoughServlet.class, "/notenough/*");
+ _context.addServlet(UnavailableServlet.class, "/unavailable/*");
+ _context.addServlet(DeleteServlet.class, "/delete/*");
+ _context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*");
HandlerWrapper noopHandler = new HandlerWrapper()
{
@@ -106,10 +110,10 @@ public class ErrorPageTest
super.handle(target, baseRequest, request, response);
}
};
- context.insertHandler(noopHandler);
+ _context.insertHandler(noopHandler);
_errorPageErrorHandler = new ErrorPageErrorHandler();
- context.setErrorHandler(_errorPageErrorHandler);
+ _context.setErrorHandler(_errorPageErrorHandler);
_errorPageErrorHandler.addErrorPage(595, "/error/595");
_errorPageErrorHandler.addErrorPage(597, "/sync");
_errorPageErrorHandler.addErrorPage(599, "/error/599");
@@ -408,6 +412,46 @@ public class ErrorPageTest
assertThat(response, Matchers.endsWith("SomeBytes"));
}
+ @Test
+ public void testPermanentlyUnavailable() throws Exception
+ {
+ try (StacklessLogging ignore = new StacklessLogging(_context.getLogger()))
+ {
+ try (StacklessLogging ignore2 = new StacklessLogging(HttpChannel.class))
+ {
+ __destroyed = new AtomicBoolean(false);
+ String response = _connector.getResponse("GET /unavailable/info HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 404 "));
+ assertTrue(__destroyed.get());
+ }
+ }
+ }
+
+ @Test
+ public void testUnavailable() throws Exception
+ {
+ try (StacklessLogging ignore = new StacklessLogging(_context.getLogger()))
+ {
+ try (StacklessLogging ignore2 = new StacklessLogging(HttpChannel.class))
+ {
+ __destroyed = new AtomicBoolean(false);
+ String response = _connector.getResponse("GET /unavailable/info?for=1 HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 503 "));
+ assertFalse(__destroyed.get());
+
+ response = _connector.getResponse("GET /unavailable/info?ok=true HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 503 "));
+ assertFalse(__destroyed.get());
+
+ Thread.sleep(1500);
+
+ response = _connector.getResponse("GET /unavailable/info?ok=true HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 200 "));
+ assertFalse(__destroyed.get());
+ }
+ }
+ }
+
public static class AppServlet extends HttpServlet implements Servlet
{
@Override
@@ -618,6 +662,34 @@ public class ErrorPageTest
}
}
+ public static class UnavailableServlet extends HttpServlet implements Servlet
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ String ok = request.getParameter("ok");
+ if (Boolean.parseBoolean(ok))
+ {
+ response.setStatus(200);
+ response.flushBuffer();
+ return;
+ }
+
+ String f = request.getParameter("for");
+ if (f == null)
+ throw new UnavailableException("testing permanent");
+
+ throw new UnavailableException("testing periodic", Integer.parseInt(f));
+ }
+
+ @Override
+ public void destroy()
+ {
+ if (__destroyed != null)
+ __destroyed.set(true);
+ }
+ }
+
public static class SingleDispatchFilter implements Filter
{
ConcurrentMap dispatches = new ConcurrentHashMap<>();
@@ -665,7 +737,6 @@ public class ErrorPageTest
@Override
public void destroy()
{
-
}
}
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
index 6441cffb7b1..d1f6bc6e05b 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
@@ -285,6 +285,34 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString("UTF8"));
}
+ @Test
+ public void testGzipHandlerWithMultipleAcceptEncodingHeaders() throws Exception
+ {
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+
+ request.setMethod("GET");
+ request.setURI("/ctx/content?vary=Accept-Encoding,Other");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host", "tester");
+ request.setHeader("accept-encoding", "deflate");
+ request.setHeader("accept-encoding", "gzip");
+
+ response = HttpTester.parseResponse(_connector.getResponse(request.generate()));
+
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.get("Content-Encoding"), Matchers.equalToIgnoringCase("gzip"));
+ assertThat(response.get("ETag"), is(__contentETagGzip));
+ assertThat(response.getCSV("Vary", false), Matchers.contains("Accept-Encoding", "Other"));
+
+ InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
+ ByteArrayOutputStream testOut = new ByteArrayOutputStream();
+ IO.copy(testIn, testOut);
+
+ assertEquals(__content, testOut.toString("UTF8"));
+ }
+
@Test
public void testGzipNotMicro() throws Exception
{
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java
deleted file mode 100644
index d29eb5baa67..00000000000
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2019 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.
-// ========================================================================
-//
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.eclipse.jetty.servlet;
-
-import java.util.Collections;
-import java.util.Set;
-import javax.servlet.ServletRegistration;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- *
- */
-public class HolderTest
-{
-
- @Test
- public void testInitParams() throws Exception
- {
- ServletHolder holder = new ServletHolder(Source.JAVAX_API);
- ServletRegistration reg = holder.getRegistration();
-
- assertThrows(IllegalArgumentException.class,() -> reg.setInitParameter(null, "foo"));
-
- assertThrows(IllegalArgumentException.class,() -> reg.setInitParameter("foo", null));
-
- reg.setInitParameter("foo", "bar");
- assertFalse(reg.setInitParameter("foo", "foo"));
-
- Set clash = reg.setInitParameters(Collections.singletonMap("foo", "bax"));
- assertTrue(clash != null && clash.size() == 1, "should be one clash");
-
- assertThrows(IllegalArgumentException.class,() -> reg.setInitParameters(Collections.singletonMap((String)null, "bax")));
- assertThrows(IllegalArgumentException.class,() -> reg.setInitParameters(Collections.singletonMap("foo", (String)null)));
-
- Set clash2 = reg.setInitParameters(Collections.singletonMap("FOO", "bax"));
- assertTrue(clash2.isEmpty(), "should be no clash");
- assertEquals(2, reg.getInitParameters().size(), "setInitParameters should not replace existing non-clashing init parameters");
- }
-}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
index 9a938ad9b30..080e045eca2 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
@@ -667,7 +667,7 @@ public class ServletContextHandlerTest
}
@Test
- public void testAddServletFromFilter() throws Exception
+ public void testAddServletByClassFromFilter() throws Exception
{
//A servlet cannot be added from a Filter
Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT");
@@ -718,6 +718,110 @@ public class ServletContextHandlerTest
}
}
+ @Test
+ public void testAddServletByInstanceFromFilter() throws Exception
+ {
+ //A servlet cannot be added from a Filter
+ Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT");
+
+ try (StacklessLogging stackless = new StacklessLogging(logger))
+ {
+ ServletContextHandler context = new ServletContextHandler();
+ context.setLogger(logger);
+ FilterHolder holder = new FilterHolder(new Filter()
+ {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", new HelloServlet());
+ rego.addMapping("/hello/*");
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ });
+ context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
+ context.getServletHandler().setStartWithUnavailable(false);
+ context.setContextPath("/");
+ _server.setHandler(context);
+ _server.start();
+ fail("Servlet can only be added from SCI or SCL");
+ }
+ catch (Exception e)
+ {
+ if (!(e instanceof IllegalStateException))
+ {
+ if (e instanceof ServletException)
+ {
+ assertTrue(e.getCause() instanceof IllegalStateException);
+ }
+ else
+ fail(e);
+ }
+ }
+ }
+
+ @Test
+ public void testAddServletByClassNameFromFilter() throws Exception
+ {
+ //A servlet cannot be added from a Filter
+ Logger logger = Log.getLogger(ContextHandler.class.getName() + "ROOT");
+
+ try (StacklessLogging stackless = new StacklessLogging(logger))
+ {
+ ServletContextHandler context = new ServletContextHandler();
+ context.setLogger(logger);
+ FilterHolder holder = new FilterHolder(new Filter()
+ {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", HelloServlet.class.getName());
+ rego.addMapping("/hello/*");
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ });
+ context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
+ context.getServletHandler().setStartWithUnavailable(false);
+ context.setContextPath("/");
+ _server.setHandler(context);
+ _server.start();
+ fail("Servlet can only be added from SCI or SCL");
+ }
+ catch (Exception e)
+ {
+ if (!(e instanceof IllegalStateException))
+ {
+ if (e instanceof ServletException)
+ {
+ assertTrue(e.getCause() instanceof IllegalStateException);
+ }
+ else
+ fail(e);
+ }
+ }
+ }
+
@Test
public void testAddServletFromSCL() throws Exception
{
@@ -770,6 +874,7 @@ public class ServletContextHandlerTest
rego.addMapping("/hello/*");
}
}
+
root.addBean(new MySCIStarter(root.getServletContext(), new ServletAddingSCI()), true);
_server.start();
@@ -797,7 +902,6 @@ public class ServletContextHandlerTest
request.append("\n");
String response = _connector.getResponse(request.toString());
- int result;
assertThat("Response", response, containsString("Test"));
context.addServlet(HelloServlet.class, "/hello");
@@ -950,7 +1054,6 @@ public class ServletContextHandlerTest
request.append("\n");
String response = _connector.getResponse(request.toString());
- int result;
assertThat("Response", response, containsString("Test"));
assertEquals(extra, context.getSessionHandler().getHandler());
@@ -995,7 +1098,6 @@ public class ServletContextHandlerTest
request.append("\n");
String response = _connector.getResponse(request.toString());
- int result;
assertThat("Response", response, containsString("Test"));
context.stop();
@@ -1016,7 +1118,7 @@ public class ServletContextHandlerTest
@Test
public void testSetSecurityHandler() throws Exception
{
- ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS|ServletContextHandler.SECURITY|ServletContextHandler.GZIP);
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY | ServletContextHandler.GZIP);
assertNotNull(context.getSessionHandler());
SessionHandler sessionHandler = context.getSessionHandler();
assertNotNull(context.getSecurityHandler());
@@ -1094,7 +1196,6 @@ public class ServletContextHandlerTest
request.append("\n");
String response = _connector.getResponse(request.toString());
- int result;
assertThat("Response", response, containsString("Test"));
context.stop();
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java
index f9553980962..59474c2d978 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHolderTest.java
@@ -18,11 +18,11 @@
package org.eclipse.jetty.servlet;
-import javax.servlet.ServletException;
+import java.util.Collections;
+import java.util.Set;
+import javax.servlet.ServletRegistration;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.MultiException;
@@ -32,18 +32,41 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import java.io.IOException;
-
public class ServletHolderTest
{
-
public static class FakeServlet extends HttpServlet
{
}
-
+
+ @Test
+ public void testInitParams() throws Exception
+ {
+ ServletHolder holder = new ServletHolder(Source.JAVAX_API);
+ ServletRegistration reg = holder.getRegistration();
+
+ assertThrows(IllegalArgumentException.class, () -> reg.setInitParameter(null, "foo"));
+
+ assertThrows(IllegalArgumentException.class, () -> reg.setInitParameter("foo", null));
+
+ reg.setInitParameter("foo", "bar");
+ assertFalse(reg.setInitParameter("foo", "foo"));
+
+ Set clash = reg.setInitParameters(Collections.singletonMap("foo", "bax"));
+ assertTrue(clash != null && clash.size() == 1, "should be one clash");
+
+ assertThrows(IllegalArgumentException.class, () -> reg.setInitParameters(Collections.singletonMap((String)null, "bax")));
+ assertThrows(IllegalArgumentException.class, () -> reg.setInitParameters(Collections.singletonMap("foo", (String)null)));
+
+ Set clash2 = reg.setInitParameters(Collections.singletonMap("FOO", "bax"));
+ assertTrue(clash2.isEmpty(), "should be no clash");
+ assertEquals(2, reg.getInitParameters().size(), "setInitParameters should not replace existing non-clashing init parameters");
+ }
@Test
public void testTransitiveCompareTo() throws Exception
@@ -78,26 +101,16 @@ public class ServletHolderTest
ServletHolder h = new ServletHolder();
h.setName("test");
- assertEquals(null, h.getClassNameForJsp(null));
-
- assertEquals(null, h.getClassNameForJsp(""));
-
- assertEquals(null, h.getClassNameForJsp("/blah/"));
-
- assertEquals(null, h.getClassNameForJsp("//blah///"));
-
- assertEquals(null, h.getClassNameForJsp("/a/b/c/blah/"));
-
+ assertNull(h.getClassNameForJsp(null));
+ assertNull(h.getClassNameForJsp(""));
+ assertNull(h.getClassNameForJsp("/blah/"));
+ assertNull(h.getClassNameForJsp("//blah///"));
+ assertNull(h.getClassNameForJsp("/a/b/c/blah/"));
assertEquals("org.apache.jsp.a.b.c.blah", h.getClassNameForJsp("/a/b/c/blah"));
-
assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("/blah.jsp"));
-
assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("//blah.jsp"));
-
assertEquals("org.apache.jsp.blah_jsp", h.getClassNameForJsp("blah.jsp"));
-
assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("/a/b/c/blah.jsp"));
-
assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("a/b/c/blah.jsp"));
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java
new file mode 100644
index 00000000000..01100eb8b03
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java
@@ -0,0 +1,229 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 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.servlet;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Decorator;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class ServletLifeCycleTest
+{
+ static final Queue events = new ConcurrentLinkedQueue<>();
+
+ @Test
+ public void testLifeCycle() throws Exception
+ {
+ Server server = new Server(0);
+ LocalConnector connector = new LocalConnector(server);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler(server, "/");
+
+ context.getObjectFactory().addDecorator(new TestDecorator());
+
+ ServletHandler sh = context.getServletHandler();
+ sh.addListener(new ListenerHolder(TestListener.class));
+ context.addEventListener(context.getServletContext().createListener(TestListener2.class));
+
+ sh.addFilterWithMapping(TestFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
+ sh.addFilterWithMapping(new FilterHolder(context.getServletContext().createFilter(TestFilter2.class)), "/*", EnumSet.of(DispatcherType.REQUEST));
+
+ sh.addServletWithMapping(TestServlet.class, "/1/*").setInitOrder(1);
+ sh.addServletWithMapping(TestServlet2.class, "/2/*").setInitOrder(-1);
+ sh.addServletWithMapping(new ServletHolder(context.getServletContext().createServlet(TestServlet3.class))
+ {{
+ setInitOrder(1);
+ }}, "/3/*");
+
+ assertThat(events, Matchers.contains(
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2",
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3"));
+
+ events.clear();
+ server.start();
+ assertThat(events, Matchers.contains(
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener",
+ "ContextInitialized class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2",
+ "ContextInitialized class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener",
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
+ "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
+ "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
+ "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
+ "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3"));
+
+ events.clear();
+ connector.getResponse("GET /2/info HTTP/1.0\r\n\r\n");
+
+ assertThat(events, Matchers.contains(
+ "Decorate class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2",
+ "init class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2",
+ "doFilter class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
+ "doFilter class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
+ "service class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2"));
+
+ events.clear();
+ server.stop();
+
+ assertThat(events, Matchers.contains(
+ "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener",
+ "contextDestroyed class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener2",
+ "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2",
+ "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3",
+ "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2",
+ "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
+ "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet",
+ "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestListener"));
+
+ // Listener added before start is not destroyed
+ EventListener[] listeners = context.getEventListeners();
+ assertThat(listeners.length, is(1));
+ assertThat(listeners[0].getClass(), is(TestListener2.class));
+ }
+
+ public static class TestDecorator implements Decorator
+ {
+ @Override
+ public T decorate(T o)
+ {
+ events.add("Decorate " + o.getClass());
+ return o;
+ }
+
+ @Override
+ public void destroy(Object o)
+ {
+ events.add("Destroy " + o.getClass());
+ }
+ }
+
+ public static class TestListener implements ServletContextListener
+ {
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ events.add("ContextInitialized " + this.getClass());
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ events.add("contextDestroyed " + this.getClass());
+ }
+ }
+
+ public static class TestListener2 extends TestListener
+ {
+ }
+
+ public static class TestFilter implements Filter
+ {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ events.add("init " + this.getClass());
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+ {
+ events.add("doFilter " + this.getClass());
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy()
+ {
+ events.add("destroy " + this.getClass());
+ }
+ }
+
+ public static class TestFilter2 extends TestFilter
+ {
+ }
+
+ public static class TestServlet implements Servlet
+ {
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ events.add("init " + this.getClass());
+ }
+
+ @Override
+ public ServletConfig getServletConfig()
+ {
+ return null;
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ events.add("service " + this.getClass());
+ }
+
+ @Override
+ public String getServletInfo()
+ {
+ return null;
+ }
+
+ @Override
+ public void destroy()
+ {
+ events.add("destroy " + this.getClass());
+ }
+ }
+
+ public static class TestServlet2 extends TestServlet
+ {
+ }
+
+ public static class TestServlet3 extends TestServlet
+ {
+ }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
index 9e82816f2be..1fc1d9523a4 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.util;
import java.io.IOException;
-import org.eclipse.jetty.util.thread.Locker;
+import org.eclipse.jetty.util.thread.AutoLock;
/**
* This specialized callback implements a pattern that allows
@@ -125,7 +125,7 @@ public abstract class IteratingCallback implements Callback
SUCCEEDED
}
- private Locker _locker = new Locker();
+ private final AutoLock _lock = new AutoLock();
private State _state;
private boolean _iterate;
@@ -188,35 +188,31 @@ public abstract class IteratingCallback implements Callback
{
boolean process = false;
- loop:
- while (true)
+ try (AutoLock lock = _lock.lock())
{
- try (Locker.Lock lock = _locker.lock())
+ switch (_state)
{
- switch (_state)
- {
- case PENDING:
- case CALLED:
- // process will be called when callback is handled
- break loop;
+ case PENDING:
+ case CALLED:
+ // process will be called when callback is handled
+ break;
- case IDLE:
- _state = State.PROCESSING;
- process = true;
- break loop;
+ case IDLE:
+ _state = State.PROCESSING;
+ process = true;
+ break;
- case PROCESSING:
- _iterate = true;
- break loop;
+ case PROCESSING:
+ _iterate = true;
+ break;
- case FAILED:
- case SUCCEEDED:
- break loop;
+ case FAILED:
+ case SUCCEEDED:
+ break;
- case CLOSED:
- default:
- throw new IllegalStateException(toString());
- }
+ case CLOSED:
+ default:
+ throw new IllegalStateException(toString());
}
}
if (process)
@@ -243,11 +239,11 @@ public abstract class IteratingCallback implements Callback
catch (Throwable x)
{
failed(x);
- break processing;
+ break;
}
// acted on the action we have just received
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -295,18 +291,11 @@ public abstract class IteratingCallback implements Callback
case CALLED:
{
- switch (action)
- {
- case SCHEDULED:
- {
- // we lost the race, so we have to keep processing
- _state = State.PROCESSING;
- continue processing;
- }
-
- default:
- throw new IllegalStateException(String.format("%s[action=%s]", this, action));
- }
+ if (action != Action.SCHEDULED)
+ throw new IllegalStateException(String.format("%s[action=%s]", this, action));
+ // we lost the race, so we have to keep processing
+ _state = State.PROCESSING;
+ continue processing;
}
case SUCCEEDED:
@@ -335,7 +324,7 @@ public abstract class IteratingCallback implements Callback
public void succeeded()
{
boolean process = false;
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -375,7 +364,7 @@ public abstract class IteratingCallback implements Callback
public void failed(Throwable x)
{
boolean failure = false;
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -405,7 +394,7 @@ public abstract class IteratingCallback implements Callback
public void close()
{
String failure = null;
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -434,7 +423,7 @@ public abstract class IteratingCallback implements Callback
*/
boolean isIdle()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.IDLE;
}
@@ -442,7 +431,7 @@ public abstract class IteratingCallback implements Callback
public boolean isClosed()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.CLOSED;
}
@@ -453,7 +442,7 @@ public abstract class IteratingCallback implements Callback
*/
public boolean isFailed()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.FAILED;
}
@@ -464,7 +453,7 @@ public abstract class IteratingCallback implements Callback
*/
public boolean isSucceeded()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _state == State.SUCCEEDED;
}
@@ -481,7 +470,7 @@ public abstract class IteratingCallback implements Callback
*/
public boolean reset()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
index a8626a2bc27..75b9aa9f74f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
@@ -34,27 +34,39 @@ public abstract class AbstractLifeCycle implements LifeCycle
{
private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
- public static final String STOPPED = "STOPPED";
- public static final String FAILED = "FAILED";
- public static final String STARTING = "STARTING";
- public static final String STARTED = "STARTED";
- public static final String STOPPING = "STOPPING";
- public static final String RUNNING = "RUNNING";
+ enum State
+ {
+ STOPPED,
+ STARTING,
+ STARTED,
+ STOPPING,
+ FAILED
+ }
+
+ public static final String STOPPED = State.STOPPED.toString();
+ public static final String FAILED = State.FAILED.toString();
+ public static final String STARTING = State.STARTING.toString();
+ public static final String STARTED = State.STARTED.toString();
+ public static final String STOPPING = State.STOPPING.toString();
private final CopyOnWriteArrayList _listeners = new CopyOnWriteArrayList();
private final Object _lock = new Object();
- private static final int STATE_FAILED = -1;
- private static final int STATE_STOPPED = 0;
- private static final int STATE_STARTING = 1;
- private static final int STATE_STARTED = 2;
- private static final int STATE_STOPPING = 3;
- private volatile int _state = STATE_STOPPED;
+ private volatile State _state = State.STOPPED;
private long _stopTimeout = 30000;
+ /**
+ * Method to override to start the lifecycle
+ * @throws StopException If thrown, the lifecycle will immediately be stopped.
+ * @throws Exception If there was a problem starting. Will cause a transition to FAILED state
+ */
protected void doStart() throws Exception
{
}
+ /**
+ * Method to override to stop the lifecycle
+ * @throws Exception If there was a problem stopping. Will cause a transition to FAILED state
+ */
protected void doStop() throws Exception
{
}
@@ -66,11 +78,31 @@ public abstract class AbstractLifeCycle implements LifeCycle
{
try
{
- if (_state == STATE_STARTED || _state == STATE_STARTING)
- return;
- setStarting();
- doStart();
- setStarted();
+ switch (_state)
+ {
+ case STARTED:
+ return;
+
+ case STARTING:
+ case STOPPING:
+ throw new IllegalStateException(getState());
+
+ default:
+ try
+ {
+ setStarting();
+ doStart();
+ setStarted();
+ }
+ catch (StopException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(e);
+ setStopping();
+ doStop();
+ setStopped();
+ }
+ }
}
catch (Throwable e)
{
@@ -87,11 +119,20 @@ public abstract class AbstractLifeCycle implements LifeCycle
{
try
{
- if (_state == STATE_STOPPING || _state == STATE_STOPPED)
- return;
- setStopping();
- doStop();
- setStopped();
+ switch (_state)
+ {
+ case STOPPED:
+ return;
+
+ case STARTING:
+ case STOPPING:
+ throw new IllegalStateException(getState());
+
+ default:
+ setStopping();
+ doStop();
+ setStopped();
+ }
}
catch (Throwable e)
{
@@ -104,39 +145,45 @@ public abstract class AbstractLifeCycle implements LifeCycle
@Override
public boolean isRunning()
{
- final int state = _state;
-
- return state == STATE_STARTED || state == STATE_STARTING;
+ final State state = _state;
+ switch (state)
+ {
+ case STARTED:
+ case STARTING:
+ return true;
+ default:
+ return false;
+ }
}
@Override
public boolean isStarted()
{
- return _state == STATE_STARTED;
+ return _state == State.STARTED;
}
@Override
public boolean isStarting()
{
- return _state == STATE_STARTING;
+ return _state == State.STARTING;
}
@Override
public boolean isStopping()
{
- return _state == STATE_STOPPING;
+ return _state == State.STOPPING;
}
@Override
public boolean isStopped()
{
- return _state == STATE_STOPPED;
+ return _state == State.STOPPED;
}
@Override
public boolean isFailed()
{
- return _state == STATE_FAILED;
+ return _state == State.FAILED;
}
@Override
@@ -154,85 +201,73 @@ public abstract class AbstractLifeCycle implements LifeCycle
@ManagedAttribute(value = "Lifecycle State for this instance", readonly = true)
public String getState()
{
- switch (_state)
- {
- case STATE_FAILED:
- return FAILED;
- case STATE_STARTING:
- return STARTING;
- case STATE_STARTED:
- return STARTED;
- case STATE_STOPPING:
- return STOPPING;
- case STATE_STOPPED:
- return STOPPED;
- default:
- return null;
- }
+ return _state.toString();
}
public static String getState(LifeCycle lc)
{
+ if (lc instanceof AbstractLifeCycle)
+ return ((AbstractLifeCycle)lc)._state.toString();
if (lc.isStarting())
- return STARTING;
+ return State.STARTING.toString();
if (lc.isStarted())
- return STARTED;
+ return State.STARTED.toString();
if (lc.isStopping())
- return STOPPING;
+ return State.STOPPING.toString();
if (lc.isStopped())
- return STOPPED;
- return FAILED;
+ return State.STOPPED.toString();
+ return State.FAILED.toString();
}
private void setStarted()
{
- _state = STATE_STARTED;
- if (LOG.isDebugEnabled())
- LOG.debug(STARTED + " @{}ms {}", Uptime.getUptime(), this);
- for (Listener listener : _listeners)
+ if (_state == State.STARTING)
{
- listener.lifeCycleStarted(this);
+ _state = State.STARTED;
+ if (LOG.isDebugEnabled())
+ LOG.debug("STARTED @{}ms {}", Uptime.getUptime(), this);
+ for (Listener listener : _listeners)
+ listener.lifeCycleStarted(this);
}
}
private void setStarting()
{
if (LOG.isDebugEnabled())
- LOG.debug("starting {}", this);
- _state = STATE_STARTING;
+ LOG.debug("STARTING {}", this);
+ _state = State.STARTING;
for (Listener listener : _listeners)
- {
listener.lifeCycleStarting(this);
- }
}
private void setStopping()
{
if (LOG.isDebugEnabled())
- LOG.debug("stopping {}", this);
- _state = STATE_STOPPING;
+ LOG.debug("STOPPING {}", this);
+ _state = State.STOPPING;
for (Listener listener : _listeners)
- {
listener.lifeCycleStopping(this);
- }
}
private void setStopped()
{
- _state = STATE_STOPPED;
- if (LOG.isDebugEnabled())
- LOG.debug("{} {}", STOPPED, this);
- for (Listener listener : _listeners)
+ if (_state == State.STOPPING)
{
- listener.lifeCycleStopped(this);
+ _state = State.STOPPED;
+ if (LOG.isDebugEnabled())
+ LOG.debug("STOPPED {}", this);
+ for (Listener listener : _listeners)
+ {
+ listener.lifeCycleStopped(this);
+ }
}
}
private void setFailed(Throwable th)
{
- _state = STATE_FAILED;
+ _state = State.FAILED;
if (LOG.isDebugEnabled())
- LOG.warn(FAILED + " " + this + ": " + th, th);
+ LOG.warn("FAILED " + this + ": " + th, th);
for (Listener listener : _listeners)
{
listener.lifeCycleFailure(this, th);
@@ -290,4 +325,10 @@ public abstract class AbstractLifeCycle implements LifeCycle
}
return String.format("%s@%x{%s}", name, hashCode(), getState());
}
+
+ /**
+ * An exception, which if thrown by doStart will immediately stop the component
+ */
+ public class StopException extends RuntimeException
+ {}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
index 188e5d65dc3..689f2a51d6f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
@@ -100,6 +100,8 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
{
for (Bean b : _beans)
{
+ if (!isStarting())
+ break;
if (b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
@@ -127,8 +129,6 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
}
}
}
-
- super.doStart();
}
catch (Throwable th)
{
@@ -193,6 +193,8 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
MultiException mex = new MultiException();
for (Bean b : reverse)
{
+ if (!isStopping())
+ break;
if (b._managed == Managed.MANAGED && b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java
index 2660ee326fb..7408e042e17 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java
@@ -21,17 +21,39 @@ package org.eclipse.jetty.util.component;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
-/* A Lifecycle that can be gracefully shutdown.
+/**
+ * Jetty components that wish to be part of a Graceful shutdown implement this interface so that
+ * the {@link Graceful#shutdown()} method will be called to initiate a shutdown. Shutdown operations
+ * can fall into the following categories:
+ *
+ * - Preventing new load from being accepted (eg connectors stop accepting connections)
+ * - Preventing existing load expanding (eg stopping existing connections accepting new requests)
+ * - Waiting for existing load to complete (eg waiting for active request count to reduce to 0)
+ * - Performing cleanup operations that may take time (eg closing an SSL connection)
+ *
+ * The {@link Future} returned by the the shutdown call will be completed to indicate the shutdown operation is completed.
+ * Some shutdown operations may be instantaneous and always return a completed future.
+ *
+ * Graceful shutdown is typically orchestrated by the doStop methods of Server or ContextHandler (for a full or partial
+ * shutdown respectively).
+ *
*/
public interface Graceful
{
- public Future shutdown();
+ Future shutdown();
- public boolean isShutdown();
+ boolean isShutdown();
- public static class Shutdown implements Graceful
+ /**
+ * A utility Graceful that uses a {@link FutureCallback} to indicate if shutdown is completed.
+ * By default the {@link FutureCallback} is returned as already completed, but the {@link #newShutdownCallback()} method
+ * can be overloaded to return a non-completed callback that will require a {@link Callback#succeeded()} or
+ * {@link Callback#failed(Throwable)} call to be completed.
+ */
+ class Shutdown implements Graceful
{
private final AtomicReference _shutdown = new AtomicReference<>();
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
index 9ed6d39e286..6a46f76e2c0 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
@@ -42,6 +42,7 @@ public class Constraint implements Cloneable, Serializable
public static final String __SPNEGO_AUTH = "SPNEGO";
public static final String __NEGOTIATE_AUTH = "NEGOTIATE";
+ public static final String __OPENID_AUTH = "OPENID";
public static boolean validateMethod(String method)
{
@@ -54,7 +55,8 @@ public class Constraint implements Cloneable, Serializable
method.equals(__CERT_AUTH) ||
method.equals(__CERT_AUTH2) ||
method.equals(__SPNEGO_AUTH) ||
- method.equals(__NEGOTIATE_AUTH));
+ method.equals(__NEGOTIATE_AUTH) ||
+ method.equals(__OPENID_AUTH));
}
public static final int DC_UNSET = -1;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/AutoLock.java
similarity index 68%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java
rename to jetty-util/src/main/java/org/eclipse/jetty/util/thread/AutoLock.java
index b56180bd90e..9bcab7eff2a 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Locker.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/AutoLock.java
@@ -22,37 +22,27 @@ import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
- * Convenience auto closeable {@link java.util.concurrent.locks.ReentrantLock} wrapper.
- *
+ * Reentrant lock that can be used in a try-with-resources statement.
*
- * try (Locker.Lock lock = locker.lock())
+ * try (AutoLock lock = this.lock.lock())
* {
- * // something
+ * // Something
* }
*
*/
-public class Locker
+public class AutoLock implements AutoCloseable
{
private final ReentrantLock _lock = new ReentrantLock();
- private final Lock _unlock = new Lock();
/**
* Acquires the lock.
*
- * @return the lock to unlock
+ * @return this AutoLock for unlocking
*/
- public Lock lock()
+ public AutoLock lock()
{
_lock.lock();
- return _unlock;
- }
-
- /**
- * @return whether this lock has been acquired
- */
- public boolean isLocked()
- {
- return _lock.isLocked();
+ return this;
}
/**
@@ -63,15 +53,15 @@ public class Locker
return _lock.newCondition();
}
- /**
- * The unlocker object that unlocks when it is closed.
- */
- public class Lock implements AutoCloseable
+ // Package-private for testing only.
+ boolean isLocked()
{
- @Override
- public void close()
- {
- _lock.unlock();
- }
+ return _lock.isLocked();
+ }
+
+ @Override
+ public void close()
+ {
+ _lock.unlock();
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index b518634488d..fb9c17c7447 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -176,7 +176,6 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
removeBean(_tryExecutor);
_tryExecutor = TryExecutor.NO_TRY;
-
// Signal the Runner threads that we are stopping
int threads = _counts.getAndSetHi(Integer.MIN_VALUE);
@@ -483,7 +482,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
public void execute(Runnable job)
{
// Determine if we need to start a thread, use and idle thread or just queue this job
- boolean startThread;
+ int startThread;
while (true)
{
// Get the atomic counts
@@ -501,10 +500,10 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
// Start a thread if we have insufficient idle threads to meet demand
// and we are not at max threads.
- startThread = (idle <= 0 && threads < _maxThreads);
+ startThread = (idle <= 0 && threads < _maxThreads) ? 1 : 0;
// The job will be run by an idle thread when available
- if (!_counts.compareAndSet(counts, threads + (startThread ? 1 : 0), idle - 1))
+ if (!_counts.compareAndSet(counts, threads + startThread, idle + startThread - 1))
continue;
break;
@@ -513,7 +512,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
if (!_jobs.offer(job))
{
// reverse our changes to _counts.
- if (addCounts(startThread ? -1 : 0, 1))
+ if (addCounts(-startThread, 1 - startThread))
LOG.warn("{} rejected {}", this, job);
throw new RejectedExecutionException(job.toString());
}
@@ -522,7 +521,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
LOG.debug("queue {} startThread={}", job, startThread);
// Start a thread if one was needed
- if (startThread)
+ while (startThread-- > 0)
startThread();
}
@@ -617,7 +616,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
if (threads < _minThreads || (idle < 0 && threads < _maxThreads))
{
// Then try to start a thread.
- if (_counts.compareAndSet(counts, threads + 1, idle))
+ if (_counts.compareAndSet(counts, threads + 1, idle + 1))
startThread();
// Otherwise continue to check state again.
continue;
@@ -645,7 +644,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
finally
{
if (!started)
- addCounts(-1, 0); // threads, idle
+ addCounts(-1, -1); // threads, idle
}
}
@@ -849,13 +848,8 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
LOG.debug("Runner started for {}", QueuedThreadPool.this);
Runnable job = null;
-
try
{
- // All threads start idle (not yet taken a job)
- if (!addCounts(0, 1))
- return;
-
while (true)
{
// If we had a job, signal that we are idle again
@@ -863,6 +857,7 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
{
if (!addCounts(0, 1))
break;
+ job = null;
}
// else check we are still running
else if (_counts.getHi() == Integer.MIN_VALUE)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
index 3c7fa26e90b..7480e2442a2 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
@@ -81,8 +81,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
_executor = executor;
_capacity = reservedThreads(executor, capacity);
_stack = new ConcurrentLinkedDeque<>();
-
- LOG.debug("{}", this);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", this);
}
/**
@@ -155,6 +155,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
public void doStart() throws Exception
{
_lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _capacity);
+ _size.set(0);
super.doStart();
}
@@ -163,15 +164,29 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
{
if (_lease != null)
_lease.close();
+
+ super.doStop();
+
while (true)
{
+ int size = _size.get();
+ // If no reserved threads left try setting size to -1 to
+ // atomically prevent other threads adding themselves to stack.
+ if (size == 0 && _size.compareAndSet(size, -1))
+ break;
+
ReservedThread thread = _stack.pollFirst();
if (thread == null)
- break;
+ {
+ // Reserved thread must have incremented size but not yet added itself to queue.
+ // We will spin until it is added.
+ Thread.onSpinWait();
+ continue;
+ }
+
_size.decrementAndGet();
thread.stop();
}
- super.doStop();
}
@Override
@@ -194,9 +209,10 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
return false;
ReservedThread thread = _stack.pollFirst();
- if (thread == null && task != STOP)
+ if (thread == null)
{
- startReservedThread();
+ if (task != STOP)
+ startReservedThread();
return false;
}
@@ -248,8 +264,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
private class ReservedThread implements Runnable
{
- private final Locker _locker = new Locker();
- private final Condition _wakeup = _locker.newCondition();
+ private final AutoLock _lock = new AutoLock();
+ private final Condition _wakeup = _lock.newCondition();
private boolean _starting = true;
private Runnable _task = null;
@@ -258,7 +274,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
if (LOG.isDebugEnabled())
LOG.debug("{} offer {}", this, task);
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
_task = task;
_wakeup.signal();
@@ -275,12 +291,13 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
if (LOG.isDebugEnabled())
LOG.debug("{} waiting", this);
- Runnable task = null;
- while (task == null)
+ while (true)
{
- boolean idle = false;
+ if (!isRunning())
+ return STOP;
- try (Locker.Lock lock = _locker.lock())
+ boolean idle = false;
+ try (AutoLock lock = _lock.lock())
{
if (_task == null)
{
@@ -296,8 +313,16 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
LOG.ignore(e);
}
}
- task = _task;
- _task = null;
+ else
+ {
+ Runnable task = _task;
+ _task = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} task={}", this, task);
+
+ return task;
+ }
}
if (idle)
@@ -312,11 +337,6 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
tryExecute(STOP);
}
}
-
- if (LOG.isDebugEnabled())
- LOG.debug("{} task={}", this, task);
-
- return task;
}
@Override
@@ -329,6 +349,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
while (true)
{
int size = _size.get();
+ if (size < 0)
+ return;
if (size >= _capacity)
{
if (LOG.isDebugEnabled())
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
index a0c889d162c..891b6f6d9f7 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
@@ -22,7 +22,10 @@ import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
@@ -40,6 +43,8 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch
private final boolean daemon;
private final ClassLoader classloader;
private final ThreadGroup threadGroup;
+ private final int threads;
+ private final AtomicInteger count = new AtomicInteger();
private volatile ScheduledThreadPoolExecutor scheduler;
private volatile Thread thread;
@@ -50,28 +55,48 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch
public ScheduledExecutorScheduler(String name, boolean daemon)
{
- this(name, daemon, Thread.currentThread().getContextClassLoader());
+ this(name, daemon, null);
}
- public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader)
+ public ScheduledExecutorScheduler(@Name("name") String name, @Name("daemon") boolean daemon, @Name("threads") int threads)
{
- this(name, daemon, threadFactoryClassLoader, null);
+ this(name, daemon, null, null, threads);
}
- public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader, ThreadGroup threadGroup)
+ public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader classLoader)
{
- this.name = name == null ? "Scheduler-" + hashCode() : name;
+ this(name, daemon, classLoader, null);
+ }
+
+ public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader classLoader, ThreadGroup threadGroup)
+ {
+ this(name, daemon, classLoader, threadGroup, -1);
+ }
+
+ /**
+ * @param name The name of the scheduler threads or null for automatic name
+ * @param daemon True if scheduler threads should be daemon
+ * @param classLoader The classloader to run the threads with or null to use the current thread context classloader
+ * @param threadGroup The threadgroup to use or null for no thread group
+ * @param threads The number of threads to pass to the the core {@link ScheduledThreadPoolExecutor} or -1 for a
+ * heuristic determined number of threads.
+ */
+ public ScheduledExecutorScheduler(@Name("name") String name, @Name("daemon") boolean daemon, @Name("classLoader") ClassLoader classLoader, @Name("threadGroup") ThreadGroup threadGroup, @Name("threads") int threads)
+ {
+ this.name = StringUtil.isBlank(name) ? "Scheduler-" + hashCode() : name;
this.daemon = daemon;
- this.classloader = threadFactoryClassLoader == null ? Thread.currentThread().getContextClassLoader() : threadFactoryClassLoader;
+ this.classloader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
this.threadGroup = threadGroup;
+ this.threads = threads;
}
@Override
protected void doStart() throws Exception
{
- scheduler = new ScheduledThreadPoolExecutor(1, r ->
+ int size = threads > 0 ? threads : 1;
+ scheduler = new ScheduledThreadPoolExecutor(size, r ->
{
- Thread thread = ScheduledExecutorScheduler.this.thread = new Thread(threadGroup, r, name);
+ Thread thread = ScheduledExecutorScheduler.this.thread = new Thread(threadGroup, r, name + "-" + count.incrementAndGet());
thread.setDaemon(daemon);
thread.setContextClassLoader(classloader);
return thread;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java
index d32a779f543..bb14efbeb76 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java
@@ -22,11 +22,10 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
-import org.eclipse.jetty.util.thread.Locker;
-import org.eclipse.jetty.util.thread.Locker.Lock;
/**
* A strategy where the thread that produces will always run the resulting task.
@@ -45,7 +44,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
{
private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class);
- private final Locker _locker = new Locker();
+ private final AutoLock _lock = new AutoLock();
private final Runnable _runProduce = new RunProduce();
private final Producer _producer;
private final Executor _executor;
@@ -67,7 +66,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
LOG.debug("{} execute", this);
boolean produce = false;
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
// If we are idle and a thread is not producing
if (_idle)
@@ -98,7 +97,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
if (LOG.isDebugEnabled())
LOG.debug("{} spawning", this);
boolean dispatch = false;
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
if (_idle)
dispatch = true;
@@ -115,7 +114,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
if (LOG.isDebugEnabled())
LOG.debug("{} run", this);
boolean produce = false;
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
_pending = false;
if (!_idle && !_producing)
@@ -145,7 +144,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
LOG.debug("{} produced {}", this, task);
boolean dispatch = false;
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
// Finished producing
_producing = false;
@@ -191,13 +190,12 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
// Run the task.
if (LOG.isDebugEnabled())
LOG.debug("{} run {}", this, task);
- if (task != null)
- task.run();
+ task.run();
if (LOG.isDebugEnabled())
LOG.debug("{} ran {}", this, task);
// Once we have run the task, we can try producing again.
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
// Is another thread already producing or we are now idle?
if (_producing || _idle)
@@ -212,7 +210,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
public Boolean isIdle()
{
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
return _idle;
}
@@ -223,7 +221,7 @@ public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
{
StringBuilder builder = new StringBuilder();
builder.append("EPC ");
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
builder.append(_idle ? "Idle/" : "");
builder.append(_producing ? "Prod/" : "");
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java
index f37a94a40cf..15de66f6694 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java
@@ -22,8 +22,8 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
-import org.eclipse.jetty.util.thread.Locker;
/**
* A strategy where the caller thread iterates over task production, submitting each
@@ -33,7 +33,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable
{
private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class);
- private final Locker _locker = new Locker();
+ private final AutoLock _lock = new AutoLock();
private final Producer _producer;
private final Executor _executor;
private State _state = State.IDLE;
@@ -47,7 +47,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable
@Override
public void produce()
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -73,7 +73,7 @@ public class ProduceConsume implements ExecutionStrategy, Runnable
if (task == null)
{
- try (Locker.Lock lock = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java
index 0b7dc16c7bf..a648cc4e118 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java
@@ -22,11 +22,10 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
-import org.eclipse.jetty.util.thread.Locker;
-import org.eclipse.jetty.util.thread.Locker.Lock;
/**
*
A strategy where the caller thread iterates over task production, submitting each
@@ -36,7 +35,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy
{
private static final Logger LOG = Log.getLogger(ProduceExecuteConsume.class);
- private final Locker _locker = new Locker();
+ private final AutoLock _lock = new AutoLock();
private final Producer _producer;
private final Executor _executor;
private State _state = State.IDLE;
@@ -50,7 +49,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy
@Override
public void produce()
{
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -77,7 +76,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy
if (task == null)
{
- try (Lock locked = _locker.lock())
+ try (AutoLock lock = _lock.lock())
{
switch (_state)
{
@@ -106,7 +105,7 @@ public class ProduceExecuteConsume implements ExecutionStrategy
@Override
public void dispatch()
{
- _executor.execute(() -> produce());
+ _executor.execute(this::produce);
}
private enum State
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java
similarity index 71%
rename from jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java
rename to jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java
index 967969d771d..3b388d0771e 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/LockerTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AutoLockTest.java
@@ -26,19 +26,15 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class LockerTest
+public class AutoLockTest
{
- public LockerTest()
- {
- }
-
@Test
public void testLocked()
{
- Locker lock = new Locker();
+ AutoLock lock = new AutoLock();
assertFalse(lock.isLocked());
- try (Locker.Lock l = lock.lock())
+ try (AutoLock l = lock.lock())
{
assertTrue(lock.isLocked());
}
@@ -53,10 +49,10 @@ public class LockerTest
@Test
public void testLockedException()
{
- Locker lock = new Locker();
+ AutoLock lock = new AutoLock();
assertFalse(lock.isLocked());
- try (Locker.Lock l = lock.lock())
+ try (AutoLock l = lock.lock())
{
assertTrue(lock.isLocked());
throw new Exception();
@@ -76,27 +72,23 @@ public class LockerTest
@Test
public void testContend() throws Exception
{
- final Locker lock = new Locker();
+ AutoLock lock = new AutoLock();
final CountDownLatch held0 = new CountDownLatch(1);
final CountDownLatch hold0 = new CountDownLatch(1);
- Thread thread0 = new Thread()
+ Thread thread0 = new Thread(() ->
{
- @Override
- public void run()
+ try (AutoLock l = lock.lock())
{
- try (Locker.Lock l = lock.lock())
- {
- held0.countDown();
- hold0.await();
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
+ held0.countDown();
+ hold0.await();
}
- };
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ });
thread0.start();
held0.await();
@@ -104,22 +96,18 @@ public class LockerTest
final CountDownLatch held1 = new CountDownLatch(1);
final CountDownLatch hold1 = new CountDownLatch(1);
- Thread thread1 = new Thread()
+ Thread thread1 = new Thread(() ->
{
- @Override
- public void run()
+ try (AutoLock l = lock.lock())
{
- try (Locker.Lock l = lock.lock())
- {
- held1.countDown();
- hold1.await();
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
+ held1.countDown();
+ hold1.await();
}
- };
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ });
thread1.start();
// thread1 will be spinning here
assertFalse(held1.await(100, TimeUnit.MILLISECONDS));
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
index edcd78a8137..892addbdeff 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
@@ -45,6 +45,61 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
private static final Logger LOG = Log.getLogger(QueuedThreadPoolTest.class);
private final AtomicInteger _jobs = new AtomicInteger();
+ private static class TestQueuedThreadPool extends QueuedThreadPool
+ {
+ private final AtomicInteger _started;
+ private final CountDownLatch _enteredRemoveThread;
+ private final CountDownLatch _exitRemoveThread;
+
+ public TestQueuedThreadPool(AtomicInteger started, CountDownLatch enteredRemoveThread, CountDownLatch exitRemoveThread)
+ {
+ _started = started;
+ _enteredRemoveThread = enteredRemoveThread;
+ _exitRemoveThread = exitRemoveThread;
+ }
+
+ public void superStartThread()
+ {
+ super.startThread();
+ }
+
+ @Override
+ protected void startThread()
+ {
+ switch (_started.incrementAndGet())
+ {
+ case 1:
+ case 2:
+ case 3:
+ super.startThread();
+ break;
+
+ case 4:
+ // deliberately not start thread
+ break;
+
+ default:
+ throw new IllegalStateException("too many threads started");
+ }
+ }
+
+ @Override
+ protected void removeThread(Thread thread)
+ {
+ try
+ {
+ _enteredRemoveThread.countDown();
+ _exitRemoveThread.await();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ super.removeThread(thread);
+ }
+ }
+
private class RunningJob implements Runnable
{
final CountDownLatch _run = new CountDownLatch(1);
@@ -450,6 +505,63 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
tp.stop();
}
+ @Test
+ public void testEnsureThreads() throws Exception
+ {
+ AtomicInteger started = new AtomicInteger(0);
+
+ CountDownLatch enteredRemoveThread = new CountDownLatch(1);
+ CountDownLatch exitRemoveThread = new CountDownLatch(1);
+ TestQueuedThreadPool tp = new TestQueuedThreadPool(started, enteredRemoveThread, exitRemoveThread);
+
+ tp.setMinThreads(2);
+ tp.setMaxThreads(10);
+ tp.setIdleTimeout(400);
+ tp.setThreadsPriority(Thread.NORM_PRIORITY - 1);
+
+ tp.start();
+ waitForIdle(tp, 2);
+ waitForThreads(tp, 2);
+
+ RunningJob job1 = new RunningJob();
+ RunningJob job2 = new RunningJob();
+ RunningJob job3 = new RunningJob();
+ tp.execute(job1);
+ tp.execute(job2);
+ tp.execute(job3);
+
+ waitForThreads(tp, 3);
+ waitForIdle(tp, 0);
+
+ // We stop job3, the thread becomes idle, thread decides to shrink, and then blocks in removeThread().
+ job3.stop();
+ assertTrue(enteredRemoveThread.await(5, TimeUnit.SECONDS));
+ waitForThreads(tp, 3);
+ waitForIdle(tp, 1);
+
+ // Executing job4 will not start a new thread because we already have 1 idle thread.
+ RunningJob job4 = new RunningJob();
+ tp.execute(job4);
+
+ // Allow thread to exit from removeThread().
+ // The 4th thread is not actually started in our startThread() until tp.superStartThread() is called.
+ // Delay by 1000ms to check that ensureThreads is only starting one thread even though it is slow to start.
+ assertThat(started.get(), is(3));
+ exitRemoveThread.countDown();
+ Thread.sleep(1000);
+
+ // Now startThreads() should have been called 4 times.
+ // Actually start the thread, and job4 should be run.
+ assertThat(started.get(), is(4));
+ tp.superStartThread();
+ assertTrue(job4._run.await(5, TimeUnit.SECONDS));
+
+ job1.stop();
+ job2.stop();
+ job4.stop();
+ tp.stop();
+ }
+
@Test
public void testMaxStopTime() throws Exception
{
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java
index fa357cb63c3..2ed1ede0717 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/AbstractConfiguration.java
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
public class AbstractConfiguration implements Configuration
{
- private final boolean _disabledByDefault;
+ private final boolean _enabledByDefault;
private final List _after = new ArrayList<>();
private final List _beforeThis = new ArrayList<>();
private final ClassMatcher _system = new ClassMatcher();
@@ -34,12 +34,12 @@ public class AbstractConfiguration implements Configuration
protected AbstractConfiguration()
{
- this(false);
+ this(true);
}
- protected AbstractConfiguration(boolean disabledByDefault)
+ protected AbstractConfiguration(boolean enabledByDefault)
{
- _disabledByDefault = disabledByDefault;
+ _enabledByDefault = enabledByDefault;
}
/**
@@ -196,9 +196,9 @@ public class AbstractConfiguration implements Configuration
}
@Override
- public boolean isDisabledByDefault()
+ public boolean isEnabledByDefault()
{
- return _disabledByDefault;
+ return _enabledByDefault;
}
@Override
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
index 48e1bef7a26..ab1d90ae41c 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
@@ -34,12 +34,12 @@ import org.eclipse.jetty.util.TopologicalSort;
*
* Configuration instances are discovered by the {@link Configurations} class using either the
* {@link ServiceLoader} mechanism or by an explicit call to {@link Configurations#setKnown(String...)}.
- * By default, all Configurations that do not implement the {@link #isDisabledByDefault()} interface
+ * By default, all Configurations that do not return false from {@link #isEnabledByDefault()}
* are applied to all {@link WebAppContext}s within the JVM. However a Server wide default {@link Configurations}
* collection may also be defined with {@link Configurations#setServerDefault(org.eclipse.jetty.server.Server)}.
* Furthermore, each individual Context may have its Configurations list explicitly set and/or amended with
* {@link WebAppContext#setConfigurations(Configuration[])}, {@link WebAppContext#addConfiguration(Configuration...)}
- * or {@link WebAppContext#getWebAppConfigurations()}.
+ * or {@link WebAppContext#getConfigurations()}.
*
* Since Jetty-9.4, Configurations are self ordering using the {@link #getDependencies()} and
* {@link #getDependents()} methods for a {@link TopologicalSort} initiated by {@link Configurations#sort()}
@@ -171,9 +171,9 @@ public interface Configuration
void destroy(WebAppContext context) throws Exception;
/**
- * @return true if configuration is disabled by default
+ * @return true if configuration is enabled by default
*/
- boolean isDisabledByDefault();
+ boolean isEnabledByDefault();
/**
* @return true if configuration should be aborted
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java
index 9868d7f570a..2839445e540 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configurations.java
@@ -209,7 +209,7 @@ public class Configurations extends AbstractList implements Dumpa
if (configurations == null)
{
configurations = new Configurations(Configurations.getKnown().stream()
- .filter(c -> !c.isDisabledByDefault())
+ .filter(c -> c.isEnabledByDefault())
.map(c -> c.getClass().getName())
.toArray(String[]::new));
}
@@ -279,6 +279,27 @@ public class Configurations extends AbstractList implements Dumpa
}
}
+ public T get(Class extends T> configClass)
+ {
+ for (Configuration configuration : _configurations)
+ {
+ if (configClass.isAssignableFrom(configuration.getClass()))
+ return (T)configuration;
+ }
+ return null;
+ }
+
+ public List getConfigurations(Class extends T> configClass)
+ {
+ List list = new ArrayList<>();
+ for (Configuration configuration : _configurations)
+ {
+ if (configClass.isAssignableFrom(configuration.getClass()))
+ list.add((T)configuration);
+ }
+ return list;
+ }
+
public void clear()
{
_configurations.clear();
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 0758e441616..3ce9d5cbf51 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
@@ -403,7 +403,7 @@ public class MetaData
p.process(context, getWebXml());
for (WebDescriptor wd : getOverrideWebs())
{
- LOG.debug("process {} {}", context, wd);
+ LOG.debug("process {} {} {}", context, p, wd);
p.process(context, wd);
}
}
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 786fcf1b1b2..7bdb0e1a387 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
@@ -95,7 +95,7 @@ import org.eclipse.jetty.util.resource.ResourceCollection;
*
* - Add all Server class inclusions from all known configurations {@link Configurations#getKnown()}
* - {@link #loadConfigurations()}, which uses either explicitly set Configurations or takes the server
- * default (which is all known non {@link Configuration#isDisabledByDefault()} Configurations.
+ * default (which is all known {@link Configuration#isEnabledByDefault()} Configurations.
* - Sort the configurations using {@link TopologicalSort} in {@link Configurations#sort()}.
* - Add all Server class exclusions from this webapps {@link Configurations}
* - Add all System classes inclusions and exclusions for this webapps {@link Configurations}
@@ -183,10 +183,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
"org.eclipse.jetty." // hide jetty classes
);
- private final Configurations _configurations = new Configurations();
private final ClassMatcher _systemClasses = new ClassMatcher(__dftSystemClasses);
private final ClassMatcher _serverClasses = new ClassMatcher(__dftServerClasses);
+ private Configurations _configurations;
private String _defaultsDescriptor = WEB_DEFAULTS_XML;
private String _descriptor = null;
private final List _overrideDescriptors = new ArrayList<>();
@@ -570,18 +570,21 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
{
// Prepare for configuration
MultiException mx = new MultiException();
- for (Configuration configuration : _configurations)
+ if (_configurations != null)
{
- try
+ for (Configuration configuration : _configurations)
{
- configuration.destroy(this);
- }
- catch (Exception e)
- {
- mx.add(e);
+ try
+ {
+ configuration.destroy(this);
+ }
+ catch (Exception e)
+ {
+ mx.add(e);
+ }
}
}
- _configurations.clear();
+ _configurations = null;
super.destroy();
mx.ifExceptionThrowRuntime();
}
@@ -615,10 +618,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
/**
* @return Returns the configurations.
*/
- public Configurations getWebAppConfigurations()
+ public Configurations getConfigurations()
{
- if (_configurations.size() == 0)
- loadConfigurations();
+ loadConfigurations();
return _configurations;
}
@@ -885,10 +887,18 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
protected void loadConfigurations()
{
//if the configuration instances have been set explicitly, use them
- if (!_configurations.isEmpty())
+ if (_configurations != null)
return;
+ if (isStarted())
+ throw new IllegalStateException();
+ _configurations = newConfigurations();
+ }
- _configurations.add(Configurations.getServerDefault(getServer()).toArray());
+ protected Configurations newConfigurations()
+ {
+ Configurations configurations = new Configurations();
+ configurations.add(Configurations.getServerDefault(getServer()).toArray());
+ return configurations;
}
@Override
@@ -958,8 +968,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
public void setConfigurationClasses(String[] configurations)
{
- if (isStarted())
- throw new IllegalStateException();
+ if (_configurations == null)
+ _configurations = new Configurations();
_configurations.set(configurations);
}
@@ -973,15 +983,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
public void setConfigurations(Configuration[] configurations)
{
- if (isStarted())
- throw new IllegalStateException();
+ if (_configurations == null)
+ _configurations = new Configurations();
_configurations.set(configurations);
}
public void addConfiguration(Configuration... configuration)
{
- if (isStarted())
- throw new IllegalStateException();
loadConfigurations();
_configurations.add(configuration);
}
@@ -989,12 +997,19 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public T getConfiguration(Class extends T> configClass)
{
loadConfigurations();
- for (Configuration configuration : _configurations)
- {
- if (configClass.isAssignableFrom(configuration.getClass()))
- return (T)configuration;
- }
- return null;
+ return _configurations.get(configClass);
+ }
+
+ public void removeConfiguration(Configuration... configurations)
+ {
+ if (_configurations != null)
+ _configurations.remove(configurations);
+ }
+
+ public void removeConfiguration(Class extends Configuration>... configurations)
+ {
+ if (_configurations != null)
+ _configurations.remove(configurations);
}
/**
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
index a25f4e399bf..99a8c3202bd 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
@@ -38,6 +38,7 @@ public class WebInfConfiguration extends AbstractConfiguration
private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
+ public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase";
protected Resource _preUnpackBaseResource;
@@ -338,8 +339,11 @@ public class WebInfConfiguration extends AbstractConfiguration
}
if (extractedWebAppDir == null)
+ {
// Then extract it if necessary to the temporary location
extractedWebAppDir = new File(context.getTempDirectory(), "webapp");
+ context.setAttribute(TEMPORARY_RESOURCE_BASE, extractedWebAppDir);
+ }
if (webApp.getFile() != null && webApp.getFile().isDirectory())
{
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
index 20e90ef451d..04f40e0f02d 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
@@ -100,7 +100,7 @@ public class WebAppContextTest
{
Configurations.cleanKnown();
String[] known_and_enabled = Configurations.getKnown().stream()
- .filter(c -> !c.isDisabledByDefault())
+ .filter(c -> c.isEnabledByDefault())
.map(c -> c.getClass().getName())
.toArray(String[]::new);
@@ -108,7 +108,7 @@ public class WebAppContextTest
//test if no classnames set, its the defaults
WebAppContext wac = new WebAppContext();
- assertThat(wac.getWebAppConfigurations().stream()
+ assertThat(wac.getConfigurations().stream()
.map(c -> c.getClass().getName())
.collect(Collectors.toList()),
Matchers.containsInAnyOrder(known_and_enabled));
@@ -126,7 +126,7 @@ public class WebAppContextTest
Configurations.cleanKnown();
WebAppContext wac = new WebAppContext();
wac.setServer(new Server());
- assertThat(wac.getWebAppConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()),
+ assertThat(wac.getConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()),
Matchers.contains(
"org.eclipse.jetty.webapp.JmxConfiguration",
"org.eclipse.jetty.webapp.WebInfConfiguration",
@@ -144,14 +144,14 @@ public class WebAppContextTest
Configuration[] configs = {new WebInfConfiguration()};
WebAppContext wac = new WebAppContext();
wac.setConfigurations(configs);
- assertThat(wac.getWebAppConfigurations(), Matchers.contains(configs));
+ assertThat(wac.getConfigurations(), Matchers.contains(configs));
//test that explicit config instances override any from server
String[] classNames = {"x.y.z"};
Server server = new Server();
server.setAttribute(Configuration.ATTR, classNames);
wac.setServer(server);
- assertThat(wac.getWebAppConfigurations(), Matchers.contains(configs));
+ assertThat(wac.getConfigurations(), Matchers.contains(configs));
}
@Test
diff --git a/jetty-websocket/javax-websocket-client/pom.xml b/jetty-websocket/javax-websocket-client/pom.xml
index c4a74057925..bdbaea8cb23 100644
--- a/jetty-websocket/javax-websocket-client/pom.xml
+++ b/jetty-websocket/javax-websocket-client/pom.xml
@@ -29,6 +29,12 @@
org.eclipse.jetty.toolchain
jetty-javax-websocket-api