diff --git a/VERSION.txt b/VERSION.txt index dff67924351..ca5fb7e1411 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,5 +1,31 @@ jetty-10.0.0-SNAPSHOT +jetty-9.4.29.v20200521 - 21 May 2020 + + 2188 Lock contention creating HTTP/2 streams + + 4235 communicate the reason of failure to the OpenID error page + + 4695 HttpChannel recycling in h2 + + 4764 HTTP2 Jetty Server does not send back content-length + + 4778 Enforcing SNI when there are only non-wildcards certificates + + 4787 Make org.eclipse.jetty.client.HttpRequest's host name writable + + 4789 org.eclipse.jetty.util.thread.ShutdownThread should use an appropriate + name to identify itself in Thread dump + + 4798 Better handling of fatal Selector failures + + 4814 Allow a ConnectionFactory (eg SslConnectionFactory) to automatically + add a Customizer + + 4820 Jetty OSGi DefaultJettyAtJettyHomeHelper refers to non-existent + config file + + 4824 WebSocket server outgoing message queue memory growth + + 4828 NIO ByteBuffer corruption in embedded Jetty server + + 4835 GzipHandler and GzipHttpOutputInterceptor do not flush response when + body is empty + + 4860 org.eclipse.jetty.server.HttpChannel busyloop on HttpFields + NullPointerException + + 4861 Combine `AttributesMap` and `Attributes.Wrapper` + + 4868 Update to asm 7.3.1 + + 4892 Non-blocking JSON parser + + 4895 AbstractSessionCache.setFlushOnResponseCommit(true) can write an + invalid session to the backing store + jetty-10.0.0.alpha1 - 26 November 2019 + 97 Permanent UnavailableException thrown during servlet request handling should cause servlet destroy @@ -363,7 +389,7 @@ jetty-9.4.28.v20200408 - 08 April 2020 + 4529 ErrorHandler showing servlet info, can not be disabled unless overriding most of its functionality + 4542 servlet context root mapping incorrect - + 4619 Inconsistent library versions notice. + + 4619 Inconsistent library versions notice + 4620 Using console-capture with StdErrLog results in empty log file + 4621 jetty-jaspi in jetty-all uber aggregate artifact requires javax.security.auth.message.AuthException which cannot be included diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml index e70d07eeff1..feda113fe70 100644 --- a/examples/embedded/pom.xml +++ b/examples/embedded/pom.xml @@ -22,7 +22,7 @@ org.eclipse.jetty jetty-slf4j-impl - test + runtime org.eclipse.jetty diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java index 22dfd38702a..c18551268d1 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java @@ -61,6 +61,7 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.PushCacheFilter; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.LoggerFactory; public class Http2Server { @@ -74,6 +75,8 @@ public class Http2Server ManagementFactory.getPlatformMBeanServer()); server.addBean(mbContainer); + server.addBean(LoggerFactory.getILoggerFactory()); + ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); Path docroot = Paths.get("src/main/resources/docroot"); if (!Files.exists(docroot)) diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java index c310dfc0036..0ad7e061bc7 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java @@ -211,7 +211,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation for (String p : urlPatternList) { ServletMapping existingMapping = _context.getServletHandler().getServletMapping(p); - if (existingMapping != null && existingMapping.isDefault()) + if (existingMapping != null && existingMapping.isFromDefaultDescriptor()) { String[] updatedPaths = ArrayUtil.removeFromArray(existingMapping.getPathSpecs(), p); //if we removed the last path from a servletmapping, delete the servletmapping @@ -264,7 +264,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation return false; for (ServletMapping m : mappings) { - if (!m.isDefault()) + if (!m.isFromDefaultDescriptor()) return true; } return false; diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java index c8e5d29516f..3572e08185d 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java @@ -117,7 +117,7 @@ public class TestServletAnnotations ServletMapping m = new ServletMapping(); m.setPathSpec("/"); m.setServletName("default"); - m.setDefault(true); //this mapping will be from a default descriptor + m.setFromDefaultDescriptor(true); //this mapping will be from a default descriptor wac.getServletHandler().addServletMapping(m); WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); @@ -150,13 +150,13 @@ public class TestServletAnnotations ServletMapping m = new ServletMapping(); m.setPathSpec("/"); m.setServletName("default"); - m.setDefault(true); //this mapping will be from a default descriptor + m.setFromDefaultDescriptor(true); //this mapping will be from a default descriptor wac.getServletHandler().addServletMapping(m); ServletMapping m2 = new ServletMapping(); m2.setPathSpec("/other"); m2.setServletName("default"); - m2.setDefault(true); //this mapping will be from a default descriptor + m2.setFromDefaultDescriptor(true); //this mapping will be from a default descriptor wac.getServletHandler().addServletMapping(m2); WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); @@ -235,7 +235,7 @@ public class TestServletAnnotations ServletMapping m = new ServletMapping(); m.setPathSpec("/default"); - m.setDefault(true); + m.setFromDefaultDescriptor(true); m.setServletName("DServlet"); wac.getServletHandler().addServletMapping(m); diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml index 0a9c50a7b59..29eb56448e4 100644 --- a/jetty-bom/pom.xml +++ b/jetty-bom/pom.xml @@ -381,7 +381,17 @@ org.eclipse.jetty.websocket - websocket-core + websocket-core-common + 10.0.0-SNAPSHOT + + + org.eclipse.jetty.websocket + websocket-core-client + 10.0.0-SNAPSHOT + + + org.eclipse.jetty.websocket + websocket-core-server 10.0.0-SNAPSHOT diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java index bdc02f24198..bc29eea9d84 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java @@ -93,4 +93,10 @@ public class HttpAuthenticationStore implements AuthenticationStore } return null; } + + @Override + public boolean hasAuthenticationResults() + { + return !results.isEmpty(); + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 51939e50163..4f4b40a60fd 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -1113,7 +1113,7 @@ public class HttpClient extends ContainerLifeCycle protected String normalizeHost(String host) { - if (host != null && host.matches("\\[.*]")) + if (host != null && host.startsWith("[") && host.endsWith("]")) return host.substring(1, host.length() - 1); return host; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index f603d9978f4..4ebfe627497 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.AuthenticationStore; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.BytesRequestContent; @@ -138,13 +139,15 @@ public abstract class HttpConnection implements IConnection request.path(path); } - URI uri = request.getURI(); - ProxyConfiguration.Proxy proxy = destination.getProxy(); - if (proxy instanceof HttpProxy && !HttpClient.isSchemeSecure(request.getScheme()) && uri != null) + if (proxy instanceof HttpProxy && !HttpClient.isSchemeSecure(request.getScheme())) { - path = uri.toString(); - request.path(path); + URI uri = request.getURI(); + if (uri != null) + { + path = uri.toString(); + request.path(path); + } } // If we are HTTP 1.1, add the Host header @@ -185,9 +188,10 @@ public abstract class HttpConnection implements IConnection // Cookies CookieStore cookieStore = getHttpClient().getCookieStore(); - if (cookieStore != null) + if (cookieStore != null && cookieStore.getClass() != HttpCookieStore.Empty.class) { StringBuilder cookies = null; + URI uri = request.getURI(); if (uri != null) cookies = convertCookies(HttpCookieStore.matchPath(uri, cookieStore.get(uri)), null); cookies = convertCookies(request.getCookies(), cookies); @@ -199,8 +203,8 @@ public abstract class HttpConnection implements IConnection } // Authentication - applyAuthentication(request, proxy != null ? proxy.getURI() : null); - applyAuthentication(request, uri); + applyProxyAuthentication(request, proxy); + applyRequestAuthentication(request); } private StringBuilder convertCookies(List cookies, StringBuilder builder) @@ -216,16 +220,31 @@ public abstract class HttpConnection implements IConnection return builder; } - private void applyAuthentication(Request request, URI uri) + private void applyProxyAuthentication(Request request, ProxyConfiguration.Proxy proxy) { - if (uri != null) + if (proxy != null) { - Authentication.Result result = getHttpClient().getAuthenticationStore().findAuthenticationResult(uri); + Authentication.Result result = getHttpClient().getAuthenticationStore().findAuthenticationResult(proxy.getURI()); if (result != null) result.apply(request); } } + private void applyRequestAuthentication(Request request) + { + AuthenticationStore authenticationStore = getHttpClient().getAuthenticationStore(); + if (authenticationStore.hasAuthenticationResults()) + { + URI uri = request.getURI(); + if (uri != null) + { + Authentication.Result result = authenticationStore.findAuthenticationResult(uri); + if (result != null) + result.apply(request); + } + } + } + public boolean onIdleTimeout(long idleTimeout) { synchronized (this) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java index 1eddf461326..dedc5413b71 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java @@ -117,9 +117,9 @@ public class HttpConversation extends AttributesMap // will notify a listener that may send a new request and trigger // another call to this method which will build different listeners // which may be iterated over when the iteration continues. - List listeners = new ArrayList<>(); HttpExchange firstExchange = exchanges.peekFirst(); HttpExchange lastExchange = exchanges.peekLast(); + List listeners = new ArrayList<>(firstExchange.getResponseListeners().size() + lastExchange.getResponseListeners().size()); if (firstExchange == lastExchange) { // We don't have a conversation, just a single request. diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 85fe08b44d6..9891d21509b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.client; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -31,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.LongConsumer; import java.util.function.LongUnaryOperator; -import java.util.stream.Collectors; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -693,10 +693,11 @@ public abstract class HttpReceiver private ContentListeners(List responseListeners) { - listeners = responseListeners.stream() + listeners = new ArrayList<>(responseListeners.size()); + responseListeners.stream() .filter(Response.DemandedContentListener.class::isInstance) .map(Response.DemandedContentListener.class::cast) - .collect(Collectors.toList()); + .forEach(listeners::add); } private boolean isEmpty() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java index 32552fe8f72..62a3ef4775e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java @@ -75,4 +75,12 @@ public interface AuthenticationStore * @return the {@link Authentication.Result} that matches the given URI, or null */ public Authentication.Result findAuthenticationResult(URI uri); + + /** + * @return false if there are no stored authentication results, true if there may be some. + */ + public default boolean hasAuthenticationResults() + { + return true; + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index c1831b869ac..90991377ce8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -73,7 +73,10 @@ public interface Request * @param host the URI host of this request, such as "127.0.0.1" or "google.com" * @return this request object */ - Request host(String host); + default Request host(String host) + { + return this; + } /** * @return the URI port of this request such as 80 or 443 @@ -81,11 +84,13 @@ public interface Request int getPort(); /** - * * @param port the URI port of this request such as 80 or 443 * @return this request object */ - Request port(int port); + default Request port(int port) + { + return this; + } /** * @return the method of this request, such as GET or POST, as a String diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java index f575938cd4c..39089a1e801 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java @@ -67,6 +67,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; @@ -88,8 +89,9 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.WINDOWS; -// This whole test is very specific to how TLS < 1.3 works. -@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10}) +// Other JREs have slight differences in how TLS work +// and this test expects a very specific TLS behavior. +@EnabledOnJre(JRE.JAVA_11) public class SslBytesServerTest extends SslBytesTest { private final AtomicInteger sslFills = new AtomicInteger(); @@ -108,9 +110,9 @@ public class SslBytesServerTest extends SslBytesTest @BeforeEach public void init() throws Exception { - - threadPool = Executors.newCachedThreadPool(); - server = new Server(); + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); sslFills.set(0); sslFlushes.set(0); @@ -119,6 +121,8 @@ public class SslBytesServerTest extends SslBytesTest File keyStore = MavenTestingUtils.getTestResourceFile("keystore.p12"); sslContextFactory = new SslContextFactory.Server(); + // This whole test is very specific to how TLS < 1.3 works. + sslContextFactory.setIncludeProtocols("TLSv1.2"); sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath()); sslContextFactory.setKeyStorePassword("storepwd"); @@ -238,6 +242,7 @@ public class SslBytesServerTest extends SslBytesTest sslContext = sslContextFactory.getSslContext(); + threadPool = Executors.newCachedThreadPool(); proxy = new SimpleProxy(threadPool, "localhost", serverPort); proxy.start(); logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort); @@ -1128,6 +1133,66 @@ public class SslBytesServerTest extends SslBytesTest client.close(); } + @Test + public void testRequestResponseServerIdleTimeoutClientResets() throws Exception + { + SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(() -> + { + OutputStream clientOutput = client.getOutputStream(); + clientOutput.write(( + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n").getBytes(StandardCharsets.UTF_8)); + clientOutput.flush(); + return null; + }); + + // Application data + TLSRecord record = proxy.readFromClient(); + proxy.flushToServer(record); + assertNull(request.get(5, TimeUnit.SECONDS)); + + // Application data + record = proxy.readFromServer(); + assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); + String line = reader.readLine(); + assertNotNull(line); + assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Wait for the server idle timeout. + Thread.sleep(idleTimeout); + + // We expect that the server sends the TLS Alert. + record = proxy.readFromServer(); + assertNotNull(record); + assertEquals(TLSRecord.Type.ALERT, record.getType()); + + // Send a RST to the server. + proxy.sendRSTToServer(); + + // Wait for the RST to be processed by the server. + Thread.sleep(1000); + + // The server EndPoint must be closed. + assertFalse(serverEndPoint.get().isOpen()); + + client.close(); + } + @Test @EnabledOnOs(LINUX) // see message below public void testRequestWithCloseAlertWithSplitBoundary() throws Exception diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java index 72748ffded6..abc47d56f2c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java @@ -234,6 +234,7 @@ public abstract class SslBytesTest public void flushToServer(TLSRecord record, long sleep) throws Exception { + logger.debug("P --> S {}", record); if (record == null) { server.shutdownOutput(); @@ -272,6 +273,7 @@ public abstract class SslBytesTest public void flushToClient(TLSRecord record) throws Exception { + logger.debug("C <-- P {}", record); if (record == null) { client.shutdownOutput(); diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-jdbc.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-jdbc.adoc index a93320d3411..2badd001afc 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-jdbc.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/sessions/session-configuration-jdbc.adoc @@ -85,6 +85,16 @@ db-connection-type=datasource #jetty.session.jdbc.schema.maxIntervalColumn=maxInterval #jetty.session.jdbc.schema.mapColumn=map #jetty.session.jdbc.schema.table=JettySessions +# Optional name of the schema used to identify where the session table is defined in the database: +# "" - empty string, no schema name +# "INFERRED" - special string meaning infer from the current db connection +# name - a string defined by the user +#jetty.session.jdbc.schema.schemaName +# Optional name of the catalog used to identify where the session table is defined in the database: +# "" - empty string, no catalog name +# "INFERRED" - special string meaning infer from the current db connection +# name - a string defined by the user +#jetty.session.jdbc.schema.catalogName ---- jetty.session.gracePeriod.seconds:: @@ -111,4 +121,14 @@ jetty.session.jdbc.driverUrl:: Url of the database which includes the driver type, host name and port, service name and any specific attributes unique to the database, such as a username. As an example, here is a mysql connection with the username appended: `jdbc:mysql://127.0.0.1:3306/sessions?user=sessionsadmin`. -The `jetty.sessionTableSchema` values represent the names for the columns in the JDBC database and can be changed to suit your environment. +The `jetty.session.jdbc.schema.*` values represent the names of the table and columns in the JDBC database used to store sessions and can be changed to suit your environment. + +There are also two special, optional properties: `jetty.session.jdbc.schema.schemaName` and `jetty.session.jdbc.schema.catalogName`. +The exact meaning of these two properties is dependent on your database vendor, but can broadly be described as further scoping for the session table name. +See https://en.wikipedia.org/wiki/Database_schema and https://en.wikipedia.org/wiki/Database_catalog. +These extra scoping names can come into play at startup time when jetty determines if the session table already exists, or otherwise creates it on-the-fly. +If you have employed either of these concepts when you pre-created the session table, or you want to ensure that jetty uses them when it auto-creates the session table, then you have two options: either set them explicitly, or let jetty infer them from a database connection (obtained using either a Datasource or Driver according to the `db-connection-type` you have configured). +To set them explicitly, uncomment and supply appropriate values for the `jetty.session.jdbc.schema.schemaName` and/or `jetty.session.jdbc.schema.catalogName` properties. +To allow jetty to infer them from a database connection, use the special string `INFERRED` instead. +If you leave them blank or commented out, then the sessions table will not be scoped by schema or catalog name. + diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc index dc0493dabee..06c07c405de 100644 --- a/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc +++ b/jetty-documentation/src/main/asciidoc/embedded-guide/old_docs/frameworks/osgi.adoc @@ -55,7 +55,7 @@ You *must also install the Apache Aries SPI Fly bundles* as many parts of Jetty [cols=",,",options="header",] |======================================================================= |Jar |Bundle Symbolic Name |Location -|org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle-1.2.jar |org.apache.aries.spifly.dynamic.bundle +|org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle-1.2.4.jar |org.apache.aries.spifly.dynamic.bundle |https://repo1.maven.org/maven2/org/apache/aries/spifly/org.apache.aries.spifly.dynamic.bundle/[Maven central] |======================================================================= diff --git a/jetty-home/src/main/resources/modules/logging/jetty/resources/jetty-logging.properties b/jetty-home/src/main/resources/modules/logging/jetty/resources/jetty-logging.properties index d0fdf6c5bd5..fba91898dc4 100644 --- a/jetty-home/src/main/resources/modules/logging/jetty/resources/jetty-logging.properties +++ b/jetty-home/src/main/resources/modules/logging/jetty/resources/jetty-logging.properties @@ -4,5 +4,7 @@ org.eclipse.jetty.LEVEL=INFO #com.example.LEVEL=INFO ## Configure a level for specific logger #com.example.MyComponent.LEVEL=INFO +## Configure JMX Context Name +# org.eclipse.jetty.logging.jmx.context=JettyServer ## Hide stacks traces in an arbitrary logger tree #com.example.STACKS=false diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index ec9bf58c814..dbcecee56bf 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -43,7 +43,7 @@ public class HttpField if (_header != null && name == null) _name = _header.asString(); else - _name = Objects.requireNonNull(name); + _name = Objects.requireNonNull(name, "name"); _value = value; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index f8390b79a23..4924bb78b17 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -27,6 +27,7 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.function.ToIntFunction; import java.util.stream.Collectors; @@ -855,6 +856,8 @@ public interface HttpFields extends Iterable */ public Mutable put(String name, List list) { + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(list, "list must not be null"); remove(name); for (String v : list) { @@ -1018,6 +1021,9 @@ public interface HttpFields extends Iterable @Override public void add(HttpField field) { + if (field == null) + return; + _fields = Arrays.copyOf(_fields, _fields.length + 1); System.arraycopy(_fields, _cursor, _fields, _cursor + 1, _size++); _fields[_cursor++] = field; @@ -1083,7 +1089,10 @@ public interface HttpFields extends Iterable { if (_current < 0) throw new IllegalStateException(); - _fields[_current] = field; + if (field == null) + remove(); + else + _fields[_current] = field; } } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldTest.java index 6db122dced0..2816756dcbb 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldTest.java @@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.is; 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; public class HttpFieldTest @@ -165,6 +166,12 @@ public class HttpFieldTest assertEquals("c", values[2]); } + @Test + public void testFieldNameNull() + { + assertThrows(NullPointerException.class, () -> new HttpField((String)null, null)); + } + @Test public void testCachedField() { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index d9b9b39008d..ca8cc8dca15 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.Enumeration; @@ -49,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class HttpFieldsTest { @Test - public void testPut() throws Exception + public void testPut() { HttpFields.Mutable header = HttpFields.build() .put("name0", "value:0") @@ -73,13 +74,13 @@ public class HttpFieldsTest assertEquals(2, matches); e = header.getValues("name0"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value:0"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test - public void testPutTo() throws Exception + public void testPutTo() { HttpFields.Mutable header = HttpFields.build() .put("name0", "value0") @@ -99,7 +100,7 @@ public class HttpFieldsTest } @Test - public void testImmutable() throws Exception + public void testImmutable() { HttpFields header = HttpFields.build() .put("name0", "value0") @@ -109,24 +110,21 @@ public class HttpFieldsTest assertEquals("value0", header.get("Name0")); assertEquals("value1", header.get("name1")); assertEquals("value1", header.get("Name1")); - assertEquals(null, header.get("Name2")); + assertNull(header.get("Name2")); assertEquals("value0", header.getField("name0").getValue()); assertEquals("value0", header.getField("Name0").getValue()); assertEquals("value1", header.getField("name1").getValue()); assertEquals("value1", header.getField("Name1").getValue()); - assertEquals(null, header.getField("Name2")); + assertNull(header.getField("Name2")); assertEquals("value0", header.getField(0).getValue()); assertEquals("value1", header.getField(1).getValue()); - assertThrows(NoSuchElementException.class, () -> - { - header.getField(2); - }); + assertThrows(NoSuchElementException.class, () -> header.getField(2)); } @Test - public void testMutable() throws Exception + public void testMutable() { HttpFields headers = HttpFields.build() .add(HttpHeader.ETAG, "tag") @@ -147,20 +145,20 @@ public class HttpFieldsTest } @Test - public void testMap() throws Exception + public void testMap() { - Map map = new HashMap<>(); - map.put(HttpFields.build().add("X","1").add(HttpHeader.ETAG,"tag").asImmutable(),"1"); - map.put(HttpFields.build().add("X","2").add(HttpHeader.ETAG,"other").asImmutable(),"2"); + Map map = new HashMap<>(); + map.put(HttpFields.build().add("X", "1").add(HttpHeader.ETAG, "tag").asImmutable(), "1"); + map.put(HttpFields.build().add("X", "2").add(HttpHeader.ETAG, "other").asImmutable(), "2"); - assertThat(map.get(HttpFields.build().add("X","1").add(HttpHeader.ETAG,"tag").asImmutable()), is("1")); - assertThat(map.get(HttpFields.build().add("X","2").add(HttpHeader.ETAG,"other").asImmutable()), is("2")); - assertThat(map.get(HttpFields.build().add("X","2").asImmutable()), nullValue()); - assertThat(map.get(HttpFields.build().add("X","2").add(HttpHeader.ETAG,"tag").asImmutable()), nullValue()); + assertThat(map.get(HttpFields.build().add("X", "1").add(HttpHeader.ETAG, "tag").asImmutable()), is("1")); + assertThat(map.get(HttpFields.build().add("X", "2").add(HttpHeader.ETAG, "other").asImmutable()), is("2")); + assertThat(map.get(HttpFields.build().add("X", "2").asImmutable()), nullValue()); + assertThat(map.get(HttpFields.build().add("X", "2").add(HttpHeader.ETAG, "tag").asImmutable()), nullValue()); } @Test - public void testGet() throws Exception + public void testGet() { HttpFields header = HttpFields.build() .put("name0", "value0") @@ -170,24 +168,21 @@ public class HttpFieldsTest assertEquals("value0", header.get("Name0")); assertEquals("value1", header.get("name1")); assertEquals("value1", header.get("Name1")); - assertEquals(null, header.get("Name2")); + assertNull(header.get("Name2")); assertEquals("value0", header.getField("name0").getValue()); assertEquals("value0", header.getField("Name0").getValue()); assertEquals("value1", header.getField("name1").getValue()); assertEquals("value1", header.getField("Name1").getValue()); - assertEquals(null, header.getField("Name2")); + assertNull(header.getField("Name2")); assertEquals("value0", header.getField(0).getValue()); assertEquals("value1", header.getField(1).getValue()); - assertThrows(NoSuchElementException.class, () -> - { - header.getField(2); - }); + assertThrows(NoSuchElementException.class, () -> header.getField(2)); } @Test - public void testGetKnown() throws Exception + public void testGetKnown() { HttpFields.Mutable header = HttpFields.build(); @@ -200,12 +195,12 @@ public class HttpFieldsTest assertEquals("value0", header.getField(HttpHeader.CONNECTION).getValue()); assertEquals("value1", header.getField(HttpHeader.ACCEPT).getValue()); - assertEquals(null, header.getField(HttpHeader.AGE)); - assertEquals(null, header.get(HttpHeader.AGE)); + assertNull(header.getField(HttpHeader.AGE)); + assertNull(header.get(HttpHeader.AGE)); } @Test - public void testCRLF() throws Exception + public void testCRLF() { HttpFields.Mutable header = HttpFields.build(); @@ -224,7 +219,7 @@ public class HttpFieldsTest } @Test - public void testCachedPut() throws Exception + public void testCachedPut() { HttpFields.Mutable header = HttpFields.build(); @@ -244,7 +239,7 @@ public class HttpFieldsTest } @Test - public void testRePut() throws Exception + public void testRePut() { HttpFields.Mutable header = HttpFields.build(); @@ -278,13 +273,13 @@ public class HttpFieldsTest assertEquals(3, matches); e = header.getValues("name1"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test - public void testRemove() throws Exception + public void testRemove() { HttpFields.Mutable header = HttpFields.build(1) .put("name0", "value0") @@ -326,11 +321,11 @@ public class HttpFieldsTest assertEquals(2, matches); e = header.getValues("name1"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test - public void testAdd() throws Exception + public void testAdd() { HttpFields.Mutable fields = HttpFields.build(); @@ -364,11 +359,11 @@ public class HttpFieldsTest assertEquals(3, matches); e = fields.getValues("name1"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "valueA"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "valueB"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test @@ -412,7 +407,7 @@ public class HttpFieldsTest } @Test - public void testGetValues() throws Exception + public void testGetValues() { HttpFields.Mutable fields = HttpFields.build(); @@ -422,37 +417,37 @@ public class HttpFieldsTest fields.add("name1", "\"value1C\",\tvalue1D"); Enumeration e = fields.getValues("name0"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0A,value0B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0C,value0D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); e = Collections.enumeration(fields.getCSV("name0", false)); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0A"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0C"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); e = Collections.enumeration(fields.getCSV("name1", false)); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1A"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value\t, 1B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1C"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test - public void testGetCSV() throws Exception + public void testGetCSV() { HttpFields.Mutable fields = HttpFields.build(); @@ -462,37 +457,37 @@ public class HttpFieldsTest fields.add("name1", "\"value1C\",\tvalue1D"); Enumeration e = fields.getValues("name0"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0A,value0B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0C,value0D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); e = Collections.enumeration(fields.getCSV("name0", false)); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0A"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0C"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value0D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); e = Collections.enumeration(fields.getCSV("name1", false)); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1A"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value\t, 1B"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1C"); - assertEquals(true, e.hasMoreElements()); + assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1D"); - assertEquals(false, e.hasMoreElements()); + assertFalse(e.hasMoreElements()); } @Test - public void testAddQuotedCSV() throws Exception + public void testAddQuotedCSV() { HttpFields.Mutable fields = HttpFields.build(); @@ -540,7 +535,7 @@ public class HttpFieldsTest } @Test - public void testGetQualityCSV() throws Exception + public void testGetQualityCSV() { HttpFields.Mutable fields = HttpFields.build(); @@ -562,7 +557,7 @@ public class HttpFieldsTest } @Test - public void testGetQualityCSVHeader() throws Exception + public void testGetQualityCSVHeader() { HttpFields.Mutable fields = HttpFields.build(); @@ -584,7 +579,7 @@ public class HttpFieldsTest } @Test - public void testDateFields() throws Exception + public void testDateFields() { HttpFields.Mutable fields = HttpFields.build(); @@ -626,7 +621,7 @@ public class HttpFieldsTest } @Test - public void testNegDateFields() throws Exception + public void testNegDateFields() { HttpFields.Mutable fields = HttpFields.build(); @@ -644,7 +639,7 @@ public class HttpFieldsTest } @Test - public void testLongFields() throws Exception + public void testLongFields() { HttpFields.Mutable header = HttpFields.build(); @@ -656,47 +651,12 @@ public class HttpFieldsTest header.put("N2", "xx"); long i1 = header.getLongField("I1"); - try - { - header.getLongField("I2"); - assertTrue(false); - } - catch (NumberFormatException e) - { - assertTrue(true); - } - + assertThrows(NumberFormatException.class, () -> header.getLongField("I2")); long i3 = header.getLongField("I3"); - try - { - header.getLongField("I4"); - assertTrue(false); - } - catch (NumberFormatException e) - { - assertTrue(true); - } - - try - { - header.getLongField("N1"); - assertTrue(false); - } - catch (NumberFormatException e) - { - assertTrue(true); - } - - try - { - header.getLongField("N2"); - assertTrue(false); - } - catch (NumberFormatException e) - { - assertTrue(true); - } + assertThrows(NumberFormatException.class, () -> header.getLongField("I4")); + assertThrows(NumberFormatException.class, () -> header.getLongField("N1")); + assertThrows(NumberFormatException.class, () -> header.getLongField("N2")); assertEquals(42, i1); assertEquals(-44, i3); @@ -708,7 +668,7 @@ public class HttpFieldsTest } @Test - public void testContains() throws Exception + public void testContains() { HttpFields.Mutable header = HttpFields.build(); @@ -768,6 +728,73 @@ public class HttpFieldsTest assertFalse(fields.contains(keyName), "containsKey('" + keyName + "')"); } + @Test + public void testAddNullName() + { + HttpFields.Mutable fields = HttpFields.build(); + assertThrows(NullPointerException.class, () -> fields.add((String)null, "bogus")); + assertThat(fields.size(), is(0)); + + assertThrows(NullPointerException.class, () -> fields.add((HttpHeader)null, "bogus")); + assertThat(fields.size(), is(0)); + } + + @Test + public void testPutNullName() + { + HttpFields.Mutable fields = HttpFields.build(); + assertThrows(NullPointerException.class, () -> fields.put((String)null, "bogus")); + assertThat(fields.size(), is(0)); + + assertThrows(NullPointerException.class, () -> fields.put(null, (List)null)); + assertThat(fields.size(), is(0)); + + List emptyList = new ArrayList<>(); + assertThrows(NullPointerException.class, () -> fields.put(null, emptyList)); + assertThat(fields.size(), is(0)); + + assertThrows(NullPointerException.class, () -> fields.put((HttpHeader)null, "bogus")); + assertThat(fields.size(), is(0)); + } + + @Test + public void testPutNullValueList() + { + HttpFields.Mutable fields = HttpFields.build(); + + assertThrows(NullPointerException.class, () -> fields.put("name", (List)null)); + assertThat(fields.size(), is(0)); + } + + @Test + public void testPreventNullFieldEntry() + { + // Attempt various ways that may have put a null field in the array that + // previously caused a NPE in put. + HttpFields.Mutable fields = HttpFields.build(); + fields.add((HttpField)null); // should not result in field being added + assertThat(fields.size(), is(0)); + fields.put(null); // should not result in field being added + assertThat(fields.size(), is(0)); + fields.put("something", "else"); + assertThat(fields.size(), is(1)); + ListIterator iter = fields.listIterator(); + iter.next(); + iter.set(null); // set field to null - should result in noop + assertThat(fields.size(), is(0)); + iter.add(null); // attempt to add null entry + assertThat(fields.size(), is(0)); + fields.put("something", "other"); + assertThat(fields.size(), is(1)); + iter = fields.listIterator(); + iter.next(); + iter.remove(); // remove only entry + assertThat(fields.size(), is(0)); + fields.put("something", "other"); + assertThat(fields.size(), is(1)); + fields.clear(); + } + @Test public void testPreventNullField() { @@ -780,7 +807,7 @@ public class HttpFieldsTest } @Test - public void testIteration() throws Exception + public void testIteration() { HttpFields.Mutable header = HttpFields.build(); Iterator i = header.iterator(); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java index bc249bd0b2c..a5f22cc6eac 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.http2; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; @@ -47,7 +49,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable private final Queue windows = new ArrayDeque<>(); private final Deque entries = new ArrayDeque<>(); private final Queue pendingEntries = new ArrayDeque<>(); - private final Set processedEntries = new HashSet<>(); + private final Collection processedEntries = new ArrayList<>(); private final HTTP2Session session; private final ByteBufferPool.Lease lease; private Throwable terminated; @@ -192,7 +194,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable progress = true; - processedEntries.add(entry); + // We use ArrayList contains() + add() instead of HashSet add() + // because that is faster for collections of size up to 250 entries. + if (!processedEntries.contains(entry)) + processedEntries.add(entry); if (entry.getDataBytesRemaining() == 0) pending.remove(); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 546066dd610..70be6754f25 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -395,12 +395,15 @@ public class HpackContext { if (LOG.isDebugEnabled()) LOG.debug(String.format("HdrTbl[%x] evictAll", HpackContext.this.hashCode())); - _fieldMap.clear(); - _nameMap.clear(); - _offset = 0; - _size = 0; - _dynamicTableSizeInBytes = 0; - Arrays.fill(_entries, null); + if (size() > 0) + { + _fieldMap.clear(); + _nameMap.clear(); + _offset = 0; + _size = 0; + _dynamicTableSizeInBytes = 0; + Arrays.fill(_entries, null); + } } } diff --git a/jetty-infinispan/infinispan-remote-query/pom.xml b/jetty-infinispan/infinispan-remote-query/pom.xml index fb3ecfcb25d..2d531981de4 100644 --- a/jetty-infinispan/infinispan-remote-query/pom.xml +++ b/jetty-infinispan/infinispan-remote-query/pom.xml @@ -7,12 +7,11 @@ 4.0.0 infinispan-remote-query Jetty :: Infinispan Session Manager Remote with Querying - http://www.eclipse.org/jetty ${project.groupId}.infinispan.remote.query + 9.4.8.Final - install org.apache.maven.plugins @@ -127,10 +126,20 @@ infinispan-remote-query-client ${infinispan.version} + + org.slf4j + slf4j-simple + test + + + org.testcontainers + testcontainers + test + - remote + remote-session-tests hotrod.enabled @@ -144,6 +153,9 @@ maven-surefire-plugin false + + ${infinispan.docker.image.version} + diff --git a/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java b/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java index 69a6f0268e9..3f348096561 100644 --- a/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java +++ b/jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java @@ -27,6 +27,7 @@ import java.util.Random; import java.util.Set; import org.eclipse.jetty.server.session.SessionData; +import org.eclipse.jetty.session.infinispan.InfinispanSessionData; import org.eclipse.jetty.session.infinispan.QueryManager; import org.eclipse.jetty.session.infinispan.RemoteQueryManager; import org.eclipse.jetty.session.infinispan.SessionDataMarshaller; @@ -35,11 +36,19 @@ import org.hibernate.search.cfg.Environment; import org.hibernate.search.cfg.SearchMapping; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ClientIntelligence; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller; import org.infinispan.protostream.FileDescriptorSource; import org.infinispan.protostream.SerializationContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -49,8 +58,45 @@ public class RemoteQueryManagerTest { public static final String DEFAULT_CACHE_NAME = "remote-session-test"; + private static final Logger LOG = LoggerFactory.getLogger(RemoteQueryManagerTest.class); + + private static final Logger INFINISPAN_LOG = + LoggerFactory.getLogger("org.eclipse.jetty.server.session.infinispan.infinispanLogs"); + + private String host; + private int port; + + GenericContainer infinispan = + new GenericContainer(System.getProperty("infinispan.docker.image.name", "jboss/infinispan-server") + + ":" + System.getProperty("infinispan.docker.image.version", "9.4.8.Final")) + .withEnv("APP_USER","theuser") + .withEnv("APP_PASS","foobar") + .withEnv("MGMT_USER", "admin") + .withEnv("MGMT_PASS", "admin") + .waitingFor(new LogMessageWaitStrategy() + .withRegEx(".*Infinispan Server.*started in.*\\s")) + .withExposedPorts(4712,4713,8088,8089,8443,9990,9993,11211,11222,11223,11224) + .withLogConsumer(new Slf4jLogConsumer(INFINISPAN_LOG)); + + @BeforeEach + public void setup() throws Exception + { + long start = System.currentTimeMillis(); + infinispan.start(); + host = infinispan.getContainerIpAddress(); + port = infinispan.getMappedPort(11222); + LOG.info("Infinispan container started for {}:{} - {}ms", host, port, + System.currentTimeMillis() - start); + } + + @AfterEach + public void stop() throws Exception + { + infinispan.stop(); + } + @Test - public void test() throws Exception + public void testQuery() throws Exception { SearchMapping mapping = new SearchMapping(); mapping.entity(SessionData.class).indexed().providedId().property("expiry", ElementType.FIELD).field(); @@ -59,9 +105,13 @@ public class RemoteQueryManagerTest properties.put(Environment.MODEL_MAPPING, mapping); ConfigurationBuilder clientBuilder = new ConfigurationBuilder(); - clientBuilder.withProperties(properties).addServer().host("127.0.0.1").marshaller(new ProtoStreamMarshaller()); + clientBuilder.withProperties(properties).addServer() + .host(this.host).port(this.port) + .clientIntelligence(ClientIntelligence.BASIC) + .marshaller(new ProtoStreamMarshaller()); RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build()); + remoteCacheManager.administration().getOrCreateCache("remote-session-test", (String)null); FileDescriptorSource fds = new FileDescriptorSource(); fds.addProtoFiles("/session.proto"); @@ -70,20 +120,17 @@ public class RemoteQueryManagerTest serCtx.registerProtoFiles(fds); serCtx.registerMarshaller(new SessionDataMarshaller()); - RemoteCache cache = remoteCacheManager.getCache(DEFAULT_CACHE_NAME); - - ByteArrayOutputStream baos; - try (InputStream is = RemoteQueryManagerTest.class.getClassLoader().getResourceAsStream("session.proto")) + try (InputStream is = RemoteQueryManagerTest.class.getClassLoader().getResourceAsStream("session.proto"); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { if (is == null) throw new IllegalStateException("inputstream is null"); - - baos = new ByteArrayOutputStream(); IO.copy(is, baos); + String content = baos.toString("UTF-8"); + remoteCacheManager.getCache("___protobuf_metadata").put("session.proto", content); } - String content = baos.toString("UTF-8"); - remoteCacheManager.getCache("___protobuf_metadata").put("session.proto", content); + RemoteCache cache = remoteCacheManager.getCache(DEFAULT_CACHE_NAME); //put some sessions into the remote cache int numSessions = 10; @@ -97,7 +144,7 @@ public class RemoteQueryManagerTest String id = "sd" + i; //create new sessiondata with random expiry time long expiryTime = r.nextInt(maxExpiryTime); - SessionData sd = new SessionData(id, "", "", 0, 0, 0, 0); + InfinispanSessionData sd = new InfinispanSessionData(id, "", "", 0, 0, 0, 0); sd.setLastNode("lastNode"); sd.setExpiry(expiryTime); diff --git a/jetty-infinispan/infinispan-remote-query/src/test/resources/simplelogger.properties b/jetty-infinispan/infinispan-remote-query/src/test/resources/simplelogger.properties new file mode 100644 index 00000000000..0bd8aee936e --- /dev/null +++ b/jetty-infinispan/infinispan-remote-query/src/test/resources/simplelogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.infinispan.infinispanLogs=debug +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.infinispan.RemoteQueryManagerTest=debug diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java index f3200cc0cdc..39564ac64c0 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java @@ -30,10 +30,12 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import javax.management.DynamicMBean; import javax.management.InstanceNotFoundException; import javax.management.MBeanInfo; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; +import javax.management.MXBean; import javax.management.ObjectName; import javax.management.modelmbean.ModelMBean; @@ -151,6 +153,25 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De { if (o == null) return null; + if (o instanceof DynamicMBean) + return o; + Class klass = o.getClass(); + while (klass != Object.class) + { + MXBean mxbean = klass.getAnnotation(MXBean.class); + if (mxbean != null && mxbean.value()) + return o; + String mbeanName = klass.getName() + "MBean"; + String mxbeanName = klass.getName() + "MXBean"; + Class[] interfaces = klass.getInterfaces(); + for (Class type : interfaces) + { + String name = type.getName(); + if (name.equals(mbeanName) || name.equals(mxbeanName)) + return o; + } + klass = klass.getSuperclass(); + } Object mbean = findMetaData(container, o.getClass()).newInstance(o); if (mbean instanceof ObjectMBean) ((ObjectMBean)mbean).setMBeanContainer(container); @@ -338,7 +359,9 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De StringBuilder buf = new StringBuilder(); - String context = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectContextBasis()) : null; + String context = (mbean instanceof ObjectMBean) + ? makeName(((ObjectMBean)mbean).getObjectContextBasis()) + : makeName(reflectContextBasis(mbean)); if (context == null && parentObjectName != null) context = parentObjectName.getKeyProperty("context"); @@ -347,7 +370,9 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De buf.append("type=").append(type); - String name = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectNameBasis()) : context; + String name = (mbean instanceof ObjectMBean) + ? makeName(((ObjectMBean)mbean).getObjectNameBasis()) + : makeName(reflectNameBasis(mbean)); if (name != null && name.length() > 1) buf.append(",").append("name=").append(name); @@ -394,6 +419,28 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De } } + private String reflectContextBasis(Object mbean) + { + return reflectBasis(mbean, "jmxContext"); + } + + private String reflectNameBasis(Object mbean) + { + return reflectBasis(mbean, "jmxName"); + } + + private String reflectBasis(Object mbean, String methodName) + { + try + { + return (String)mbean.getClass().getMethod(methodName).invoke(mbean); + } + catch (Throwable x) + { + return null; + } + } + /** * @param basis name to strip of special characters. * @return normalized name diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java index 15aaaa512e5..961ff63ec53 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java @@ -19,21 +19,11 @@ package org.eclipse.jetty.osgi.test; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.util.StringUtil; @@ -43,16 +33,12 @@ import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.options.WrappedUrlProvisionOption.OverwriteMode; import org.ops4j.pax.tinybundles.core.TinyBundle; import org.ops4j.pax.tinybundles.core.TinyBundles; -import org.ops4j.pax.url.mvn.internal.AetherBasedResolver; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; @@ -195,7 +181,9 @@ public class TestOSGiUtil res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-jndi").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-plus").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-annotations").versionAsInProject().start()); - res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-core").versionAsInProject().start()); + res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-core-server").versionAsInProject().start()); + res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-core-client").versionAsInProject().start()); + res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-core-common").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-util").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-util-server").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("websocket-jetty-api").versionAsInProject().start()); diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java index 386c0258187..b2e13e269a4 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java @@ -106,7 +106,7 @@ public class QuickStartDescriptorProcessor extends IterativeDescriptorProcessor { String origin = node.getAttribute(_originAttributeName); if (!StringUtil.isBlank(origin) && origin.startsWith(DefaultsDescriptor.class.getSimpleName())) - mapping.setDefault(true); + mapping.setFromDefaultDescriptor(true); } } 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 ba0541986d1..5ae648a5f82 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 @@ -100,7 +100,7 @@ public class TestQuickStart server.dumpStdErr(); //verify that FooServlet is now mapped to / and not the DefaultServlet - ServletHolder sh = webapp.getServletHandler().getMappedServlet("/").getResource(); + ServletHolder sh = webapp.getServletHandler().getMappedServlet("/").getServletHolder(); assertNotNull(sh); assertEquals("foo", sh.getName()); } diff --git a/jetty-server/src/main/config/etc/sessions/jdbc/session-store.xml b/jetty-server/src/main/config/etc/sessions/jdbc/session-store.xml index 9b66771b9a8..6544aa72d33 100644 --- a/jetty-server/src/main/config/etc/sessions/jdbc/session-store.xml +++ b/jetty-server/src/main/config/etc/sessions/jdbc/session-store.xml @@ -54,6 +54,12 @@ + + + + + + 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 e97457d0781..b367e364309 100644 --- a/jetty-server/src/main/config/modules/session-store-jdbc.mod +++ b/jetty-server/src/main/config/modules/session-store-jdbc.mod @@ -55,3 +55,14 @@ db-connection-type=datasource #jetty.session.jdbc.schema.maxIntervalColumn=maxInterval #jetty.session.jdbc.schema.mapColumn=map #jetty.session.jdbc.schema.table=JettySessions +# Optional name of the schema used to identify where the session table is defined in the database: +# "" - empty string, no schema name +# "INFERRED" - special string meaning infer from the current db connection +# name - a string defined by the user +#jetty.session.jdbc.schema.schemaName +# Optional name of the catalog used to identify where the session table is defined in the database: +# "" - empty string, no catalog name +# "INFERRED" - special string meaning infer from the current db connection +# name - a string defined by the user +#jetty.session.jdbc.schema.catalogName + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncAttributes.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncAttributes.java new file mode 100644 index 00000000000..4856a99fa5d --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncAttributes.java @@ -0,0 +1,99 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.HashSet; +import java.util.Set; +import javax.servlet.AsyncContext; + +import org.eclipse.jetty.util.Attributes; + +class AsyncAttributes extends Attributes.Wrapper +{ + private final String _requestURI; + private final String _contextPath; + private final String _pathInContext; + private ServletPathMapping _mapping; + private final String _queryString; + + public AsyncAttributes(Attributes attributes, String requestUri, String contextPath, String pathInContext, ServletPathMapping mapping, String queryString) + { + super(attributes); + _requestURI = requestUri; + _contextPath = contextPath; + _pathInContext = pathInContext; + _mapping = mapping; + _queryString = queryString; + } + + @Override + public Object getAttribute(String key) + { + switch (key) + { + case AsyncContext.ASYNC_REQUEST_URI: + return _requestURI; + case AsyncContext.ASYNC_CONTEXT_PATH: + return _contextPath; + case AsyncContext.ASYNC_SERVLET_PATH: + return _mapping == null ? null : _mapping.getServletPath(); + case AsyncContext.ASYNC_PATH_INFO: + return _mapping == null ? _pathInContext : _mapping.getPathInfo(); + case AsyncContext.ASYNC_QUERY_STRING: + return _queryString; + case AsyncContext.ASYNC_MAPPING: + return _mapping; + default: + return super.getAttribute(key); + } + } + + @Override + public Set getAttributeNameSet() + { + Set set = new HashSet<>(super.getAttributeNameSet()); + set.add(AsyncContext.ASYNC_REQUEST_URI); + set.add(AsyncContext.ASYNC_CONTEXT_PATH); + set.add(AsyncContext.ASYNC_SERVLET_PATH); + set.add(AsyncContext.ASYNC_PATH_INFO); + set.add(AsyncContext.ASYNC_QUERY_STRING); + set.add(AsyncContext.ASYNC_MAPPING); + return set; + } + + @Override + public void setAttribute(String key, Object value) + { + switch (key) + { + case AsyncContext.ASYNC_REQUEST_URI: + case AsyncContext.ASYNC_CONTEXT_PATH: + case AsyncContext.ASYNC_SERVLET_PATH: + case AsyncContext.ASYNC_PATH_INFO: + case AsyncContext.ASYNC_QUERY_STRING: + case AsyncContext.ASYNC_MAPPING: + // Ignore sets for these reserved names as this class is applied + // we will always override these particular attributes. + break; + default: + super.setAttribute(key, value); + break; + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java index c4bc0095aee..76845fc9c1e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java @@ -34,7 +34,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable private final Context _context; private final AsyncContextState _asyncContext; private final HttpURI _baseURI; - private volatile HttpChannelState _state; + private final HttpChannelState _state; private ServletContext _dispatchContext; private String _dispatchPath; private volatile Scheduler.Task _timeoutTask; @@ -53,33 +53,9 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable _state = state; _baseURI = baseURI; - // If we haven't been async dispatched before - if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI) == null) - { - // We are setting these attributes during startAsync, when the spec implies that - // they are only available after a call to AsyncContext.dispatch(...); - - // have we been forwarded before? - String uri = (String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); - if (uri != null) - { - baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, uri); - baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); - baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); - baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); - baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); - baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getAttribute(RequestDispatcher.FORWARD_MAPPING)); - } - else - { - baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, baseRequest.getRequestURI()); - baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, baseRequest.getContextPath()); - baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getServletPath()); - baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getPathInfo()); - baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getQueryString()); - baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getHttpServletMapping()); - } - } + // We are setting these attributes during startAsync, when the spec implies that + // they are only available after a call to AsyncContext.dispatch(...); + baseRequest.setAsyncAttributes(); } public HttpURI getBaseURI() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index afa2fcf04b6..086ba3cb16a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -26,7 +26,6 @@ import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -101,14 +100,7 @@ public class Dispatcher implements RequestDispatcher } else { - IncludeAttributes attr = new IncludeAttributes(old_attr); - - attr._requestURI = _uri.getPath(); - attr._contextPath = _contextHandler.getContextPath(); - attr._servletPath = null; // set by ServletHandler - attr._pathInfo = _pathInContext; - attr._query = _uri.getQuery(); - attr._mapping = null; //set by ServletHandler + IncludeAttributes attr = new IncludeAttributes(old_attr, _uri.getPath(), _contextHandler.getContextPath(), _pathInContext, _uri.getQuery()); if (attr._query != null) baseRequest.mergeQueryParameters(baseRequest.getQueryString(), attr._query); baseRequest.setAttributes(attr); @@ -147,7 +139,7 @@ public class Dispatcher implements RequestDispatcher final String old_context_path = baseRequest.getContextPath(); final String old_servlet_path = baseRequest.getServletPath(); final String old_path_info = baseRequest.getPathInfo(); - final HttpServletMapping old_mapping = baseRequest.getHttpServletMapping(); + final ServletPathMapping old_mapping = baseRequest.getServletPathMapping(); final MultiMap old_query_params = baseRequest.getQueryParameters(); final Attributes old_attr = baseRequest.getAttributes(); @@ -163,30 +155,26 @@ public class Dispatcher implements RequestDispatcher } else { - ForwardAttributes attr = new ForwardAttributes(old_attr); - - //If we have already been forwarded previously, then keep using the established - //original value. Otherwise, this is the first forward and we need to establish the values. - //Note: the established value on the original request for pathInfo and - //for queryString is allowed to be null, but cannot be null for the other values. - if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null) - { - attr._pathInfo = (String)old_attr.getAttribute(FORWARD_PATH_INFO); - attr._query = (String)old_attr.getAttribute(FORWARD_QUERY_STRING); - attr._requestURI = (String)old_attr.getAttribute(FORWARD_REQUEST_URI); - attr._contextPath = (String)old_attr.getAttribute(FORWARD_CONTEXT_PATH); - attr._servletPath = (String)old_attr.getAttribute(FORWARD_SERVLET_PATH); - attr._mapping = (HttpServletMapping)old_attr.getAttribute(FORWARD_MAPPING); - } - else - { - attr._pathInfo = old_path_info; - attr._query = old_uri.getQuery(); - attr._requestURI = old_uri.getPath(); - attr._contextPath = old_context_path; - attr._servletPath = old_servlet_path; - attr._mapping = old_mapping; - } + // If we have already been forwarded previously, then keep using the established + // original value. Otherwise, this is the first forward and we need to establish the values. + // Note: the established value on the original request for pathInfo and + // for queryString is allowed to be null, but cannot be null for the other values. + // Note: the pathInfo is passed as the pathInContext since it is only used when there is + // no mapping, and when there is no mapping the pathInfo is the pathInContext. + // TODO Ultimately it is intended for the request to carry the pathInContext for easy access + ForwardAttributes attr = old_attr.getAttribute(FORWARD_REQUEST_URI) != null + ? new ForwardAttributes(old_attr, + (String)old_attr.getAttribute(FORWARD_REQUEST_URI), + (String)old_attr.getAttribute(FORWARD_CONTEXT_PATH), + (String)old_attr.getAttribute(FORWARD_PATH_INFO), + (ServletPathMapping)old_attr.getAttribute(FORWARD_MAPPING), + (String)old_attr.getAttribute(FORWARD_QUERY_STRING)) + : new ForwardAttributes(old_attr, + old_uri.getPath(), + old_context_path, + baseRequest.getPathInfo(), // TODO replace with pathInContext + old_mapping, + old_uri.getQuery()); String query = _uri.getQuery(); if (query == null) @@ -194,6 +182,7 @@ public class Dispatcher implements RequestDispatcher baseRequest.setHttpURI(HttpURI.build(old_uri, _uri.getPath(), _uri.getParam(), query)); baseRequest.setContextPath(_contextHandler.getContextPath()); + baseRequest.setServletPathMapping(null); baseRequest.setServletPath(null); baseRequest.setPathInfo(_pathInContext); @@ -257,16 +246,20 @@ public class Dispatcher implements RequestDispatcher private class ForwardAttributes extends Attributes.Wrapper { - String _requestURI; - String _contextPath; - String _servletPath; - String _pathInfo; - String _query; - HttpServletMapping _mapping; + private final String _requestURI; + private final String _contextPath; + private final String _pathInContext; + private final ServletPathMapping _servletPathMapping; + private final String _query; - ForwardAttributes(Attributes attributes) + public ForwardAttributes(Attributes attributes, String requestURI, String contextPath, String pathInContext, ServletPathMapping mapping, String query) { super(attributes); + _requestURI = requestURI; + _contextPath = contextPath; + _pathInContext = pathInContext; + _servletPathMapping = mapping; + _query = query; } @Override @@ -277,22 +270,23 @@ public class Dispatcher implements RequestDispatcher switch (key) { case FORWARD_PATH_INFO: - return _pathInfo; + return _servletPathMapping == null ? _pathInContext : _servletPathMapping.getPathInfo(); case FORWARD_REQUEST_URI: return _requestURI; case FORWARD_SERVLET_PATH: - return _servletPath; + return _servletPathMapping == null ? null : _servletPathMapping.getServletPath(); case FORWARD_CONTEXT_PATH: return _contextPath; case FORWARD_QUERY_STRING: return _query; case FORWARD_MAPPING: - return _mapping; + return _servletPathMapping; default: break; } } + // If we are forwarded then we hide include attributes if (key.startsWith(__INCLUDE_PREFIX)) return null; @@ -312,18 +306,12 @@ public class Dispatcher implements RequestDispatcher if (_named == null) { - if (_pathInfo != null) - set.add(FORWARD_PATH_INFO); - else - set.remove(FORWARD_PATH_INFO); + set.add(FORWARD_PATH_INFO); set.add(FORWARD_REQUEST_URI); set.add(FORWARD_SERVLET_PATH); set.add(FORWARD_CONTEXT_PATH); set.add(FORWARD_MAPPING); - if (_query != null) - set.add(FORWARD_QUERY_STRING); - else - set.remove(FORWARD_QUERY_STRING); + set.add(FORWARD_QUERY_STRING); } return set; @@ -332,39 +320,11 @@ public class Dispatcher implements RequestDispatcher @Override public void setAttribute(String key, Object value) { - if (_named == null && key.startsWith("javax.servlet.")) - { - switch (key) - { - case FORWARD_PATH_INFO: - _pathInfo = (String)value; - return; - case FORWARD_REQUEST_URI: - _requestURI = (String)value; - return; - case FORWARD_SERVLET_PATH: - _servletPath = (String)value; - return; - case FORWARD_CONTEXT_PATH: - _contextPath = (String)value; - return; - case FORWARD_QUERY_STRING: - _query = (String)value; - return; - case FORWARD_MAPPING: - _mapping = (HttpServletMapping)value; - return; - default: - if (value == null) - _attributes.removeAttribute(key); - else - _attributes.setAttribute(key, value); - } - } - else if (value == null) - _attributes.removeAttribute(key); - else - _attributes.setAttribute(key, value); + // Allow any attribute to be set, even if a reserved name. If a reserved + // name is set here, it will be hidden by this class during the forward, + // but revealed after the forward is complete just as if the reserved name + // attribute had be set by the application before the forward. + _attributes.setAttribute(key, value); } @Override @@ -388,16 +348,19 @@ public class Dispatcher implements RequestDispatcher private class IncludeAttributes extends Attributes.Wrapper { - String _requestURI; - String _contextPath; - String _servletPath; - String _pathInfo; - String _query; - HttpServletMapping _mapping; + private final String _requestURI; + private final String _contextPath; + private final String _pathInContext; + private ServletPathMapping _servletPathMapping; // Set later by ServletHandler + private final String _query; - IncludeAttributes(Attributes attributes) + public IncludeAttributes(Attributes attributes, String requestURI, String contextPath, String pathInContext, String query) { super(attributes); + _requestURI = requestURI; + _contextPath = contextPath; + _pathInContext = pathInContext; + _query = query; } @Override @@ -408,9 +371,9 @@ public class Dispatcher implements RequestDispatcher switch (key) { case INCLUDE_PATH_INFO: - return _pathInfo; + return _servletPathMapping == null ? _pathInContext : _servletPathMapping.getPathInfo(); case INCLUDE_SERVLET_PATH: - return _servletPath; + return _servletPathMapping == null ? null : _servletPathMapping.getServletPath(); case INCLUDE_CONTEXT_PATH: return _contextPath; case INCLUDE_QUERY_STRING: @@ -418,13 +381,11 @@ public class Dispatcher implements RequestDispatcher case INCLUDE_REQUEST_URI: return _requestURI; case INCLUDE_MAPPING: - return _mapping; + return _servletPathMapping; default: break; } } - else if (key.startsWith(__INCLUDE_PREFIX)) - return null; return _attributes.getAttribute(key); } @@ -441,18 +402,12 @@ public class Dispatcher implements RequestDispatcher if (_named == null) { - if (_pathInfo != null) - set.add(INCLUDE_PATH_INFO); - else - set.remove(INCLUDE_PATH_INFO); + set.add(INCLUDE_PATH_INFO); set.add(INCLUDE_REQUEST_URI); set.add(INCLUDE_SERVLET_PATH); set.add(INCLUDE_CONTEXT_PATH); set.add(INCLUDE_MAPPING); - if (_query != null) - set.add(INCLUDE_QUERY_STRING); - else - set.remove(INCLUDE_QUERY_STRING); + set.add(INCLUDE_QUERY_STRING); } return set; @@ -461,38 +416,11 @@ public class Dispatcher implements RequestDispatcher @Override public void setAttribute(String key, Object value) { - if (_named == null && key.startsWith("javax.servlet.")) - { - switch (key) - { - case INCLUDE_PATH_INFO: - _pathInfo = (String)value; - return; - case INCLUDE_REQUEST_URI: - _requestURI = (String)value; - return; - case INCLUDE_SERVLET_PATH: - _servletPath = (String)value; - return; - case INCLUDE_CONTEXT_PATH: - _contextPath = (String)value; - return; - case INCLUDE_QUERY_STRING: - _query = (String)value; - return; - case INCLUDE_MAPPING: - _mapping = (HttpServletMapping)value; - return; - default: - if (value == null) - _attributes.removeAttribute(key); - else - _attributes.setAttribute(key, value); - } - } - else if (value == null) - _attributes.removeAttribute(key); + if (_servletPathMapping == null && _named == null && INCLUDE_MAPPING.equals(key)) + _servletPathMapping = (ServletPathMapping)value; else + // Allow any attribute to be set, even if a reserved name. If a reserved + // name is set here, it will be revealed after the include is complete. _attributes.setAttribute(key, value); } 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 d920751f7f8..42e5455963c 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 @@ -419,8 +419,17 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor abort(x); else { - _response.resetContent(); - sendResponseAndComplete(); + try + { + _response.resetContent(); + sendResponseAndComplete(); + } + catch (Throwable t) + { + if (x != t) + x.addSuppressed(t); + abort(x); + } } } finally 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 cf16784070e..f5c9c07ad8b 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 @@ -475,8 +475,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable synchronized (_channelState) { _state = State.CLOSED; + releaseBuffer(); } - releaseBuffer(); } @Override @@ -601,10 +601,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable public ByteBuffer getBuffer() { - return _aggregate; + synchronized (_channelState) + { + return acquireBuffer(); + } } - public ByteBuffer acquireBuffer() + private ByteBuffer acquireBuffer() { if (_aggregate == null) _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.isUseOutputDirectByteBuffers()); @@ -1362,10 +1365,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable public void resetBuffer() { - _interceptor.resetBuffer(); - if (BufferUtil.hasContent(_aggregate)) - BufferUtil.clear(_aggregate); - _written = 0; + synchronized (_channelState) + { + _interceptor.resetBuffer(); + if (BufferUtil.hasContent(_aggregate)) + BufferUtil.clear(_aggregate); + _written = 0; + } } @Override 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 617816dcf37..8f39b9a7900 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 @@ -63,7 +63,6 @@ import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.MappingMatch; import javax.servlet.http.Part; import javax.servlet.http.PushBuilder; @@ -83,8 +82,6 @@ import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; @@ -193,85 +190,6 @@ public class Request implements HttpServletRequest return null; } - public static HttpServletMapping getServletMapping(PathSpec pathSpec, String servletPath, String servletName) - { - final MappingMatch match; - final String mapping; - if (pathSpec instanceof ServletPathSpec) - { - switch (pathSpec.getGroup()) - { - case ROOT: - match = MappingMatch.CONTEXT_ROOT; - mapping = ""; - break; - case DEFAULT: - match = MappingMatch.DEFAULT; - mapping = "/"; - break; - case EXACT: - match = MappingMatch.EXACT; - mapping = servletPath.startsWith("/") ? servletPath.substring(1) : servletPath; - break; - case SUFFIX_GLOB: - match = MappingMatch.EXTENSION; - int dot = servletPath.lastIndexOf('.'); - mapping = servletPath.substring(0, dot); - break; - case PREFIX_GLOB: - match = MappingMatch.PATH; - mapping = servletPath; - break; - default: - match = null; - mapping = servletPath; - break; - } - } - else - { - match = null; - mapping = servletPath; - } - - return new HttpServletMapping() - { - @Override - public String getMatchValue() - { - return (mapping == null ? "" : mapping); - } - - @Override - public String getPattern() - { - if (pathSpec != null) - return pathSpec.getDeclaration(); - return ""; - } - - @Override - public String getServletName() - { - return (servletName == null ? "" : servletName); - } - - @Override - public MappingMatch getMappingMatch() - { - return match; - } - - @Override - public String toString() - { - return "HttpServletMapping{matchValue=" + getMatchValue() + - ", pattern=" + getPattern() + ", servletName=" + getServletName() + - ", mappingMatch=" + getMappingMatch() + "}"; - } - }; - } - private final HttpChannel _channel; private final List _requestAttributeListeners = new ArrayList<>(); private final HttpInput _input; @@ -283,7 +201,7 @@ public class Request implements HttpServletRequest private String _contextPath; private String _servletPath; private String _pathInfo; - private PathSpec _pathSpec; + private ServletPathMapping _servletPathMapping; private boolean _secure; private String _asyncNotSupportedSource = null; private boolean _newContext; @@ -739,7 +657,7 @@ public class Request implements HttpServletRequest public Attributes getAttributes() { if (_attributes == null) - _attributes = new AttributesMap(); + _attributes = new ServletAttributes(); return _attributes; } @@ -1752,7 +1670,7 @@ public class Request implements HttpServletRequest // TODO this is not really right for CONNECT path = _uri.isAbsolute() ? "/" : null; else if (encoded.startsWith("/")) - path = (encoded.length() == 1) ? "/" : URIUtil.canonicalPath(URIUtil.decodePath(encoded)); + path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath(); else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod())) path = encoded; else @@ -1815,7 +1733,7 @@ public class Request implements HttpServletRequest _attributes = Attributes.unwrap(_attributes); if (_attributes != null) { - if (AttributesMap.class.equals(_attributes.getClass())) + if (ServletAttributes.class.equals(_attributes.getClass())) _attributes.clearAttributes(); else _attributes = null; @@ -1841,7 +1759,6 @@ public class Request implements HttpServletRequest _queryParameters = null; _contentParameters = null; _parameters = null; - _pathSpec = null; _contentParamsExtracted = false; _inputState = INPUT_NONE; _multiParts = null; @@ -1896,7 +1813,7 @@ public class Request implements HttpServletRequest LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent"); if (_attributes == null) - _attributes = new AttributesMap(); + _attributes = new ServletAttributes(); _attributes.setAttribute(name, value); if (!_requestAttributeListeners.isEmpty()) @@ -1919,6 +1836,76 @@ public class Request implements HttpServletRequest _attributes = attributes; } + public void setAsyncAttributes() + { + // Return if we have been async dispatched before. + if (getAttribute(AsyncContext.ASYNC_REQUEST_URI) != null) + return; + + // Unwrap the _attributes to get the base attributes instance. + Attributes baseAttributes; + if (_attributes == null) + baseAttributes = _attributes = new ServletAttributes(); + else + baseAttributes = Attributes.unwrap(_attributes); + + // We cannot use a apply AsyncAttribute via #setAttributes as that + // will wrap over any dispatch specific attribute wrappers (eg. + // Dispatcher#ForwardAttributes). Async attributes must persist + // after the current dispatch, so they must be set under any other + // wrappers. + + String fwdRequestURI = (String)getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); + if (fwdRequestURI == null) + { + if (baseAttributes instanceof ServletAttributes) + { + // The baseAttributes map is our ServletAttributes, so we can set the async + // attributes there, under any other wrappers. + ((ServletAttributes)baseAttributes).setAsyncAttributes(getRequestURI(), + getContextPath(), + getPathInfo(), // TODO change to pathInContext when cheaply available + getServletPathMapping(), + getQueryString()); + } + else + { + // We cannot find our ServletAttributes instance, so just set directly and hope + // whatever non jetty wrappers that have been applied will do the right thing. + _attributes.setAttribute(AsyncContext.ASYNC_REQUEST_URI, getRequestURI()); + _attributes.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, getContextPath()); + _attributes.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, getServletPath()); + _attributes.setAttribute(AsyncContext.ASYNC_PATH_INFO, getPathInfo()); + _attributes.setAttribute(AsyncContext.ASYNC_QUERY_STRING, getQueryString()); + _attributes.setAttribute(AsyncContext.ASYNC_MAPPING, getHttpServletMapping()); + } + } + else + { + if (baseAttributes instanceof ServletAttributes) + { + // The baseAttributes map is our ServletAttributes, so we can set the async + // attributes there, under any other wrappers. + ((ServletAttributes)baseAttributes).setAsyncAttributes(fwdRequestURI, + (String)getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH), + (String)getAttribute(RequestDispatcher.FORWARD_PATH_INFO), + (ServletPathMapping)getAttribute(RequestDispatcher.FORWARD_MAPPING), + (String)getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); + } + else + { + // We cannot find our ServletAttributes instance, so just set directly and hope + // whatever non jetty wrappers that have been applied will do the right thing. + _attributes.setAttribute(AsyncContext.ASYNC_REQUEST_URI, fwdRequestURI); + _attributes.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); + _attributes.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); + _attributes.setAttribute(AsyncContext.ASYNC_PATH_INFO, getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); + _attributes.setAttribute(AsyncContext.ASYNC_QUERY_STRING, getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); + _attributes.setAttribute(AsyncContext.ASYNC_MAPPING, getAttribute(RequestDispatcher.FORWARD_MAPPING)); + } + } + } + /** * Set the authentication. * @@ -2358,19 +2345,34 @@ public class Request implements HttpServletRequest throw new ServletException("HttpServletRequest.upgrade() not supported in Jetty"); } - public void setPathSpec(PathSpec pathSpec) + /** + * Set the servletPathMapping, the servletPath and the pathInfo. + * TODO remove the side effect on servletPath and pathInfo by removing those fields. + * @param servletPathMapping The mapping used to return from {@link #getHttpServletMapping()} + */ + public void setServletPathMapping(ServletPathMapping servletPathMapping) { - _pathSpec = pathSpec; + _servletPathMapping = servletPathMapping; + if (servletPathMapping == null) + { + // TODO reset the servletPath and pathInfo, but currently cannot do that + // as we don't know the pathInContext. + } + else + { + _servletPath = servletPathMapping.getServletPath(); + _pathInfo = servletPathMapping.getPathInfo(); + } } - public PathSpec getPathSpec() + public ServletPathMapping getServletPathMapping() { - return _pathSpec; + return _servletPathMapping; } @Override public HttpServletMapping getHttpServletMapping() { - return Request.getServletMapping(_pathSpec, _servletPath, getServletName()); + return _servletPathMapping; } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletAttributes.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletAttributes.java new file mode 100644 index 00000000000..cc25c125555 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletAttributes.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.Set; + +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; + +/** + * An implementation of Attributes that supports the standard async attributes. + * + * This implementation delegates to an internal {@link AttributesMap} instance, which + * can optionally be wrapped with a {@link AsyncAttributes} instance. This allows async + * attributes to be applied underneath any other attribute wrappers. + */ +public class ServletAttributes implements Attributes +{ + private final Attributes _attributes = new AttributesMap(); + private AsyncAttributes _asyncAttributes; + + public void setAsyncAttributes(String requestURI, String contextPath, String pathInContext, ServletPathMapping servletPathMapping, String queryString) + { + _asyncAttributes = new AsyncAttributes(_attributes, requestURI, contextPath, pathInContext, servletPathMapping, queryString); + } + + private Attributes getAttributes() + { + return (_asyncAttributes == null) ? _attributes : _asyncAttributes; + } + + @Override + public void removeAttribute(String name) + { + getAttributes().removeAttribute(name); + } + + @Override + public void setAttribute(String name, Object attribute) + { + getAttributes().setAttribute(name, attribute); + } + + @Override + public Object getAttribute(String name) + { + return getAttributes().getAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return getAttributes().getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + getAttributes().clearAttributes(); + _asyncAttributes = null; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletPathMapping.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletPathMapping.java new file mode 100644 index 00000000000..a1c5541088e --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletPathMapping.java @@ -0,0 +1,159 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import javax.servlet.http.HttpServletMapping; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.MappingMatch; + +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.ServletPathSpec; + +/** + * Implementation of HttpServletMapping. + * + * Represents the application of a {@link ServletPathSpec} to a specific path + * that resulted in a mapping to a {@link javax.servlet.Servlet}. + * As well as supporting the standard {@link HttpServletMapping} methods, this + * class also carries fields, which can be precomputed for the implementation + * of {@link HttpServletRequest#getServletPath()} and + * {@link HttpServletRequest#getPathInfo()} + */ +public class ServletPathMapping implements HttpServletMapping +{ + private final MappingMatch _mappingMatch; + private final String _matchValue; + private final String _pattern; + private final String _servletName; + private final String _servletPath; + private final String _pathInfo; + + public ServletPathMapping(PathSpec pathSpec, String servletName, String pathInContext) + { + _servletName = (servletName == null ? "" : servletName); + _pattern = pathSpec == null ? null : pathSpec.getDeclaration(); + + if (pathSpec instanceof ServletPathSpec && pathInContext != null) + { + switch (pathSpec.getGroup()) + { + case ROOT: + _mappingMatch = MappingMatch.CONTEXT_ROOT; + _matchValue = ""; + _servletPath = ""; + _pathInfo = "/"; + break; + + case DEFAULT: + _mappingMatch = MappingMatch.DEFAULT; + _matchValue = ""; + _servletPath = pathInContext; + _pathInfo = null; + break; + + case EXACT: + _mappingMatch = MappingMatch.EXACT; + _matchValue = _pattern.startsWith("/") ? _pattern.substring(1) : _pattern; + _servletPath = _pattern; + _pathInfo = null; + break; + + case PREFIX_GLOB: + _mappingMatch = MappingMatch.PATH; + _servletPath = pathSpec.getPrefix(); + // TODO avoid the substring on the known servletPath! + _matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath; + _pathInfo = pathSpec.getPathInfo(pathInContext); + break; + + case SUFFIX_GLOB: + _mappingMatch = MappingMatch.EXTENSION; + int dot = pathInContext.lastIndexOf('.'); + _matchValue = pathInContext.substring(pathInContext.startsWith("/") ? 1 : 0, dot); + _servletPath = pathInContext; + _pathInfo = null; + break; + + case MIDDLE_GLOB: + _mappingMatch = null; + _matchValue = ""; + _servletPath = pathInContext; + _pathInfo = null; + break; + + default: + throw new IllegalStateException(); + } + } + else + { + _mappingMatch = null; + _matchValue = ""; + _servletPath = pathInContext; + _pathInfo = null; + } + } + + @Override + public String getMatchValue() + { + return _matchValue; + } + + @Override + public String getPattern() + { + return _pattern; + } + + @Override + public String getServletName() + { + return _servletName; + } + + @Override + public MappingMatch getMappingMatch() + { + return _mappingMatch; + } + + public String getServletPath() + { + return _servletPath; + } + + public String getPathInfo() + { + return _pathInfo; + } + + @Override + public String toString() + { + return "ServletPathMapping{" + + "matchValue=" + _matchValue + + ", pattern=" + _pattern + + ", servletName=" + _servletName + + ", mappingMatch=" + _mappingMatch + + ", servletPath=" + _servletPath + + ", pathInfo=" + _pathInfo + + "}"; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java index a7e72064c5d..7045b1aadbb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java @@ -173,11 +173,23 @@ public class ContextHandlerCollection extends HandlerCollection @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - Handlers handlers = _handlers.get(); - if (handlers == null) + Mapping mapping = (Mapping)_handlers.get(); + + // Handle no contexts + if (mapping == null) + return; + Handler[] handlers = mapping.getHandlers(); + if (handlers == null || handlers.length == 0) return; - Mapping mapping = (Mapping)handlers; + // handle only a single context. + if (handlers.length == 1) + { + handlers[0].handle(target, baseRequest, request, response); + return; + } + + // handle async dispatch to specific context HttpChannelState async = baseRequest.getHttpChannelState(); if (async.isAsync()) { @@ -194,6 +206,7 @@ public class ContextHandlerCollection extends HandlerCollection } } + // handle many contexts if (target.startsWith("/")) { Trie> pathBranches = mapping._pathBranches; @@ -226,11 +239,9 @@ public class ContextHandlerCollection extends HandlerCollection } else { - if (mapping.getHandlers() == null) - return; - for (int i = 0; i < mapping.getHandlers().length; i++) + for (Handler handler : handlers) { - mapping.getHandlers()[i].handle(target, baseRequest, request, response); + handler.handle(target, baseRequest, request, response); if (baseRequest.isHandled()) return; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java index 87af9e2a345..3d80c9302c4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -298,7 +298,7 @@ public class ErrorHandler extends AbstractHandler // TODO error page may cause a BufferOverflow. In which case we try // TODO again with stacks disabled. If it still overflows, it is // TODO written without a body. - ByteBuffer buffer = baseRequest.getResponse().getHttpOutput().acquireBuffer(); + ByteBuffer buffer = baseRequest.getResponse().getHttpOutput().getBuffer(); ByteBufferOutputStream out = new ByteBufferOutputStream(buffer); PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset)); 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 a50daf4e2d8..a4e06c52dd5 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 @@ -467,7 +467,7 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements { //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 (session.isValid() && session.getSessionData().isDirty() && _flushOnResponseCommit) { if (LOG.isDebugEnabled()) LOG.debug("Flush session {} on response commit", session); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java index 32b6f1d2b57..279f65fe6c2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java @@ -66,9 +66,11 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore public static class SessionTableSchema { public static final int MAX_INTERVAL_NOT_SET = -999; + public static final String INFERRED = "INFERRED"; protected DatabaseAdaptor _dbAdaptor; protected String _schemaName = null; + protected String _catalogName = null; protected String _tableName = "JettySessions"; protected String _idColumn = "sessionId"; protected String _contextPathColumn = "contextPath"; @@ -87,7 +89,20 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore { _dbAdaptor = dbadaptor; } + + public void setCatalogName(String catalogName) + { + if (catalogName != null && StringUtil.isBlank(catalogName)) + _catalogName = null; + else + _catalogName = catalogName; + } + public String getCatalogName() + { + return _catalogName; + } + public String getSchemaName() { return _schemaName; @@ -95,8 +110,10 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore public void setSchemaName(String schemaName) { - checkNotNull(schemaName); - _schemaName = schemaName; + if (schemaName != null && StringUtil.isBlank(schemaName)) + _schemaName = null; + else + _schemaName = schemaName; } public String getTableName() @@ -484,28 +501,48 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore //make the session table if necessary String tableName = _dbAdaptor.convertIdentifier(getTableName()); + String schemaName = _dbAdaptor.convertIdentifier(getSchemaName()); - try (ResultSet result = metaData.getTables(null, schemaName, tableName, null)) + if (INFERRED.equalsIgnoreCase(schemaName)) + { + //use the value from the connection - + //NOTE that this value will also now be prepended to ALL + //table names in queries/updates. + schemaName = connection.getSchema(); + setSchemaName(schemaName); + } + String catalogName = _dbAdaptor.convertIdentifier(getCatalogName()); + if (INFERRED.equalsIgnoreCase(catalogName)) + { + //use the value from the connection + catalogName = connection.getCatalog(); + setCatalogName(catalogName); + } + + try (ResultSet result = metaData.getTables(catalogName, schemaName, tableName, null)) { if (!result.next()) { + if (LOG.isDebugEnabled()) + LOG.debug("Creating table {} schema={} catalog={}", tableName, schemaName, catalogName); //table does not exist, so create it statement.executeUpdate(getCreateStatementAsString()); } else { + if (LOG.isDebugEnabled()) + LOG.debug("Not creating table {} schema={} catalog={}", tableName, schemaName, catalogName); //session table exists, check it has maxinterval column ResultSet colResult = null; try { - colResult = metaData.getColumns(null, schemaName, tableName, + colResult = metaData.getColumns(catalogName, schemaName, tableName, _dbAdaptor.convertIdentifier(getMaxIntervalColumn())); } catch (SQLException sqlEx) { - LOG.warn("Problem checking if " + getTableName() + - " table contains " + getMaxIntervalColumn() + " column. Ensure table contains column definition: \"" + - getMaxIntervalColumn() + " long not null default -999\""); + LOG.warn("Problem checking if {} table contains {} column. Ensure table contains column with definition: long not null default -999", + getTableName(), getMaxIntervalColumn()); throw sqlEx; } try @@ -519,9 +556,7 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore } catch (SQLException sqlEx) { - LOG.warn("Problem adding " + getMaxIntervalColumn() + - " column. Ensure table contains column definition: \"" + getMaxIntervalColumn() + - " long not null default -999\""); + LOG.warn("Problem adding {} column. Ensure table contains column definition: long not null default -999", getMaxIntervalColumn()); throw sqlEx; } } @@ -538,7 +573,7 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore boolean index1Exists = false; boolean index2Exists = false; - try (ResultSet result = metaData.getIndexInfo(null, schemaName, tableName, false, true)) + try (ResultSet result = metaData.getIndexInfo(catalogName, schemaName, tableName, false, true)) { while (result.next()) { @@ -559,8 +594,8 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore @Override public String toString() { - return String.format("%s[%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s]", super.toString(), - _schemaName, _tableName, _idColumn, _contextPathColumn, _virtualHostColumn, _cookieTimeColumn, _createTimeColumn, + return String.format("%s[%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s]", super.toString(), + _catalogName, _schemaName, _tableName, _idColumn, _contextPathColumn, _virtualHostColumn, _cookieTimeColumn, _createTimeColumn, _expiryTimeColumn, _accessTimeColumn, _lastAccessTimeColumn, _lastNodeColumn, _lastSavedTimeColumn, _maxIntervalColumn); } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index a23c4c8aef4..a38af17e0cb 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -47,6 +47,7 @@ import javax.servlet.http.HttpServletMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.servlet.http.MappingMatch; import javax.servlet.http.Part; import javax.servlet.http.PushBuilder; @@ -86,6 +87,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -1427,69 +1429,6 @@ public class RequestTest assertThat(response, containsString("Hello World")); } - @Test - public void testHttpServletMapping() throws Exception - { - String request = "GET / HTTP/1.1\n" + - "Host: whatever\n" + - "Connection: close\n" + - "\n"; - - _server.stop(); - PathMappingHandler handler = new PathMappingHandler(null, null, null); - _server.setHandler(handler); - _server.start(); - String response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=, mappingMatch=null}")); - _server.stop(); - - ServletPathSpec spec = new ServletPathSpec(""); - handler = new PathMappingHandler(spec, spec.getPathMatch("foo"), "Something"); - _server.setHandler(handler); - _server.start(); - response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=Something, mappingMatch=CONTEXT_ROOT}")); - _server.stop(); - - spec = new ServletPathSpec("/"); - handler = new PathMappingHandler(spec, "", "Default"); - _server.setHandler(handler); - _server.start(); - response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/, pattern=/, servletName=Default, mappingMatch=DEFAULT}")); - _server.stop(); - - spec = new ServletPathSpec("/foo/*"); - handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar"), "BarServlet"); - _server.setHandler(handler); - _server.start(); - response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo, pattern=/foo/*, servletName=BarServlet, mappingMatch=PATH}")); - _server.stop(); - - spec = new ServletPathSpec("*.jsp"); - handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar.jsp"), "JspServlet"); - _server.setHandler(handler); - _server.start(); - response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo/bar, pattern=*.jsp, servletName=JspServlet, mappingMatch=EXTENSION}")); - _server.stop(); - - spec = new ServletPathSpec("/catalog"); - handler = new PathMappingHandler(spec, spec.getPathMatch("/catalog"), "CatalogServlet"); - _server.setHandler(handler); - _server.start(); - response = _connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); - assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=catalog, pattern=/catalog, servletName=CatalogServlet, mappingMatch=EXACT}")); - _server.stop(); - } - @Test public void testCookies() throws Exception { @@ -1935,6 +1874,92 @@ public class RequestTest assertThat(builder.getHeader("Cookie"), not(containsString("bad"))); } + @Test + public void testServletPathMapping() throws Exception + { + ServletPathSpec spec; + String uri; + ServletPathMapping m; + + spec = null; + uri = null; + m = new ServletPathMapping(spec, null, uri); + assertThat(m.getMappingMatch(), nullValue()); + assertThat(m.getMatchValue(), is("")); + assertThat(m.getPattern(), nullValue()); + assertThat(m.getServletName(), is("")); + assertThat(m.getServletPath(), nullValue()); + assertThat(m.getPathInfo(), nullValue()); + + spec = new ServletPathSpec(""); + uri = "/"; + m = new ServletPathMapping(spec, "Something", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.CONTEXT_ROOT)); + assertThat(m.getMatchValue(), is("")); + assertThat(m.getPattern(), is("")); + assertThat(m.getServletName(), is("Something")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + spec = new ServletPathSpec("/"); + uri = "/some/path"; + m = new ServletPathMapping(spec, "Default", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.DEFAULT)); + assertThat(m.getMatchValue(), is("")); + assertThat(m.getPattern(), is("/")); + assertThat(m.getServletName(), is("Default")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + spec = new ServletPathSpec("/foo/*"); + uri = "/foo/bar"; + m = new ServletPathMapping(spec, "FooServlet", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.PATH)); + assertThat(m.getMatchValue(), is("foo")); + assertThat(m.getPattern(), is("/foo/*")); + assertThat(m.getServletName(), is("FooServlet")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + uri = "/foo/"; + m = new ServletPathMapping(spec, "FooServlet", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.PATH)); + assertThat(m.getMatchValue(), is("foo")); + assertThat(m.getPattern(), is("/foo/*")); + assertThat(m.getServletName(), is("FooServlet")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + uri = "/foo"; + m = new ServletPathMapping(spec, "FooServlet", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.PATH)); + assertThat(m.getMatchValue(), is("foo")); + assertThat(m.getPattern(), is("/foo/*")); + assertThat(m.getServletName(), is("FooServlet")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + spec = new ServletPathSpec("*.jsp"); + uri = "/foo/bar.jsp"; + m = new ServletPathMapping(spec, "JspServlet", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.EXTENSION)); + assertThat(m.getMatchValue(), is("foo/bar")); + assertThat(m.getPattern(), is("*.jsp")); + assertThat(m.getServletName(), is("JspServlet")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + + spec = new ServletPathSpec("/catalog"); + uri = "/catalog"; + m = new ServletPathMapping(spec, "CatalogServlet", uri); + assertThat(m.getMappingMatch(), is(MappingMatch.EXACT)); + assertThat(m.getMatchValue(), is("catalog")); + assertThat(m.getPattern(), is("/catalog")); + assertThat(m.getServletName(), is("CatalogServlet")); + assertThat(m.getServletPath(), is(spec.getPathMatch(uri))); + assertThat(m.getPathInfo(), is(spec.getPathInfo(uri))); + } + interface RequestTester { boolean check(HttpServletRequest request, HttpServletResponse response) throws IOException; @@ -2169,7 +2194,6 @@ public class RequestTest { ((Request)request).setHandled(true); baseRequest.setServletPath(_servletPath); - baseRequest.setPathSpec(_spec); if (_servletName != null) baseRequest.setUserIdentityScope(new TestUserIdentityScope(null, null, _servletName)); HttpServletMapping mapping = baseRequest.getHttpServletMapping(); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 8db55db6ec0..57b9ae0d389 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -35,7 +35,6 @@ import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.server.CachedContentFactory; import org.eclipse.jetty.server.ResourceContentFactory; import org.eclipse.jetty.server.ResourceService; @@ -500,9 +499,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc if ((_welcomeServlets || _welcomeExactServlets) && welcomeServlet == null) { - MappedResource entry = _servletHandler.getMappedServlet(welcomeInContext); + ServletHandler.MappedServlet entry = _servletHandler.getMappedServlet(welcomeInContext); @SuppressWarnings("ReferenceEquality") - boolean isDefaultHolder = (entry.getResource() != _defaultHolder); + boolean isDefaultHolder = (entry.getServletHolder() != _defaultHolder); if (entry != null && isDefaultHolder && (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcomeInContext)))) welcomeServlet = welcomeInContext; diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java index 75a77c9ed0a..17ae35dea14 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -31,7 +31,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; @@ -71,7 +70,7 @@ public class Invoker extends HttpServlet private ContextHandler _contextHandler; private ServletHandler _servletHandler; - private MappedResource _invokerEntry; + private ServletHandler.MappedServlet _invokerEntry; private Map _parameters; private boolean _nonContextServlets; private boolean _verbose; @@ -171,12 +170,12 @@ public class Invoker extends HttpServlet // Check for existing mapping (avoid threaded race). String path = URIUtil.addPaths(servletPath, servlet); - MappedResource entry = _servletHandler.getMappedServlet(path); + ServletHandler.MappedServlet entry = _servletHandler.getMappedServlet(path); if (entry != null && !entry.equals(_invokerEntry)) { // Use the holder - holder = (ServletHolder)entry.getResource(); + holder = (ServletHolder)entry.getServletHolder(); } else { 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 47570d37a4f..69c2340937f 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 @@ -26,7 +26,6 @@ import java.util.EnumSet; import java.util.EventListener; import java.util.HashMap; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Queue; import java.util.Set; @@ -57,6 +56,7 @@ import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.ServletPathMapping; import org.eclipse.jetty.server.ServletRequestHttpWrapper; import org.eclipse.jetty.server.ServletResponseHttpWrapper; import org.eclipse.jetty.server.UserIdentity; @@ -92,8 +92,6 @@ public class ServletHandler extends ScopedHandler { private static final Logger LOG = LoggerFactory.getLogger(ServletHandler.class); - public static final String __DEFAULT_SERVLET = "default"; - private ServletContextHandler _contextHandler; private ServletContext _servletContext; private FilterHolder[] _filters = new FilterHolder[0]; @@ -113,8 +111,8 @@ public class ServletHandler extends ScopedHandler private List _filterPathMappings; private MultiMap _filterNameMappings; - private final Map _servletNameMap = new HashMap<>(); - private PathMappings _servletPathMap; + private final Map _servletNameMap = new HashMap<>(); + private PathMappings _servletPathMap; private ListenerHolder[] _listeners = new ListenerHolder[0]; private boolean _initialized = false; @@ -167,7 +165,7 @@ public class ServletHandler extends ScopedHandler LOG.debug("Adding Default404Servlet to {}", this); addServletWithMapping(Default404Servlet.class, "/"); updateMappings(); - getServletMapping("/").setDefault(true); + getServletMapping("/").setFromDefaultDescriptor(true); } if (isFilterChainsCached()) @@ -247,13 +245,7 @@ public class ServletHandler extends ScopedHandler //remove all of the mappings that were for non-embedded filters _filterNameMap.remove(filter.getName()); //remove any mappings associated with this filter - ListIterator fmitor = filterMappings.listIterator(); - while (fmitor.hasNext()) - { - FilterMapping fm = fmitor.next(); - if (fm.getFilterName().equals(filter.getName())) - fmitor.remove(); - } + filterMappings.removeIf(fm -> fm.getFilterName().equals(filter.getName())); } else filterHolders.add(filter); //only retain embedded @@ -261,10 +253,8 @@ public class ServletHandler extends ScopedHandler } //Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED) - FilterHolder[] fhs = (FilterHolder[])LazyList.toArray(filterHolders, FilterHolder.class); - _filters = fhs; - FilterMapping[] fms = (FilterMapping[])LazyList.toArray(filterMappings, FilterMapping.class); - _filterMappings = fms; + _filters = (FilterHolder[])LazyList.toArray(filterHolders, FilterHolder.class); + _filterMappings = (FilterMapping[])LazyList.toArray(filterMappings, FilterMapping.class); _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length - 1); _matchBeforeIndex = -1; @@ -291,13 +281,7 @@ public class ServletHandler extends ScopedHandler //remove from servlet name map _servletNameMap.remove(servlet.getName()); //remove any mappings associated with this servlet - ListIterator smitor = servletMappings.listIterator(); - while (smitor.hasNext()) - { - ServletMapping sm = smitor.next(); - if (sm.getServletName().equals(servlet.getName())) - smitor.remove(); - } + servletMappings.removeIf(sm -> sm.getServletName().equals(servlet.getName())); } else servletHolders.add(servlet); //only retain embedded @@ -305,10 +289,8 @@ public class ServletHandler extends ScopedHandler } //Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED) - ServletHolder[] shs = (ServletHolder[])LazyList.toArray(servletHolders, ServletHolder.class); - _servlets = shs; - ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); - _servletMappings = sms; + _servlets = (ServletHolder[])LazyList.toArray(servletHolders, ServletHolder.class); + _servletMappings = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); if (_contextHandler != null) _contextHandler.contextDestroyed(); @@ -332,8 +314,7 @@ public class ServletHandler extends ScopedHandler listenerHolders.add(listener); } } - ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class); - _listeners = listeners; + _listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class); //will be regenerated on next start _filterPathMappings = null; @@ -425,50 +406,40 @@ public class ServletHandler extends ScopedHandler public ServletHolder getServlet(String name) { - return _servletNameMap.get(name); + MappedServlet mapped = _servletNameMap.get(name); + if (mapped != null) + return mapped.getServletHolder(); + return null; } @Override public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Get the base requests - final String old_servlet_path = baseRequest.getServletPath(); - final String old_path_info = baseRequest.getPathInfo(); - final PathSpec old_path_spec = baseRequest.getPathSpec(); + final ServletPathMapping old_servlet_path_mapping = baseRequest.getServletPathMapping(); DispatcherType type = baseRequest.getDispatcherType(); ServletHolder servletHolder = null; UserIdentity.Scope oldScope = null; - MappedResource mapping = getMappedServlet(target); - if (mapping != null) + MappedServlet mappedServlet = getMappedServlet(target); + if (mappedServlet != null) { - servletHolder = mapping.getResource(); - - if (mapping.getPathSpec() != null) + servletHolder = mappedServlet.getServletHolder(); + ServletPathMapping servletPathMapping = mappedServlet.getServletPathMapping(target); + if (servletPathMapping != null) { - PathSpec pathSpec = mapping.getPathSpec(); - String servletPath = pathSpec.getPathMatch(target); - String pathInfo = pathSpec.getPathInfo(target); - + // Setting the servletPathMapping also provides the servletPath and pathInfo if (DispatcherType.INCLUDE.equals(type)) - { - baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPath); - baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, pathInfo); - baseRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, Request.getServletMapping(pathSpec, servletPath, servletHolder.getName())); - } + baseRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, servletPathMapping); else - { - baseRequest.setPathSpec(pathSpec); - baseRequest.setServletPath(servletPath); - baseRequest.setPathInfo(pathInfo); - } + baseRequest.setServletPathMapping(servletPathMapping); } } if (LOG.isDebugEnabled()) - LOG.debug("servlet {}|{}|{} -> {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), servletHolder); + LOG.debug("servlet {}|{}|{}|{} -> {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), baseRequest.getHttpServletMapping(), servletHolder); try { @@ -484,11 +455,7 @@ public class ServletHandler extends ScopedHandler baseRequest.setUserIdentityScope(oldScope); if (!(DispatcherType.INCLUDE.equals(type))) - { - baseRequest.setServletPath(old_servlet_path); - baseRequest.setPathInfo(old_path_info); - baseRequest.setPathSpec(old_path_spec); - } + baseRequest.setServletPathMapping(old_servlet_path_mapping); } } @@ -550,34 +517,33 @@ public class ServletHandler extends ScopedHandler } /** - * ServletHolder matching path. + * Get MappedServlet for target. * * @param target Path within _context or servlet name - * @return MappedResource to the ServletHolder. Named servlets have a null PathSpec + * @return MappedServlet matched by path or name. Named servlets have a null PathSpec */ - public MappedResource getMappedServlet(String target) + public MappedServlet getMappedServlet(String target) { if (target.startsWith("/")) { if (_servletPathMap == null) return null; - return _servletPathMap.getMatch(target); + + MappedResource match = _servletPathMap.getMatch(target); + if (match == null) + return null; + return match.getResource(); } - if (_servletNameMap == null) - return null; - ServletHolder holder = _servletNameMap.get(target); - if (holder == null) - return null; - return new MappedResource<>(null, holder); + return _servletNameMap.get(target); } - protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) + private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) { String key = pathInContext == null ? servletHolder.getName() : pathInContext; int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType()); - if (_filterChainsCached && _chainCache != null) + if (_filterChainsCached) { FilterChain chain = _chainCache[dispatch].get(key); if (chain != null) @@ -624,8 +590,7 @@ public class ServletHandler extends ScopedHandler FilterChain chain = null; if (_filterChainsCached) { - if (!filters.isEmpty()) - chain = newCachedChain(filters, servletHolder); + chain = newCachedChain(filters, servletHolder); final Map cache = _chainCache[dispatch]; final Queue lru = _chainLRU[dispatch]; @@ -648,7 +613,7 @@ public class ServletHandler extends ScopedHandler cache.put(key, chain); lru.add(key); } - else if (!filters.isEmpty()) + else chain = new Chain(baseRequest, filters, servletHolder); return chain; @@ -1133,7 +1098,7 @@ public class ServletHandler extends ScopedHandler if (mappings == null || mappings.length == 0) { setFilterMappings(insertFilterMapping(mapping, 0, false)); - if (source != null && source == Source.JAVAX_API) + if (source == Source.JAVAX_API) _matchAfterIndex = 0; } else @@ -1141,7 +1106,7 @@ public class ServletHandler extends ScopedHandler //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list. //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals), //but before the first matchAfter filtermapping. - if (source != null && Source.JAVAX_API == source) + if (Source.JAVAX_API == source) { setFilterMappings(insertFilterMapping(mapping, mappings.length - 1, false)); if (_matchAfterIndex < 0) @@ -1177,12 +1142,12 @@ public class ServletHandler extends ScopedHandler if (mappings == null || mappings.length == 0) { setFilterMappings(insertFilterMapping(mapping, 0, false)); - if (source != null && Source.JAVAX_API == source) + if (Source.JAVAX_API == source) _matchBeforeIndex = 0; } else { - if (source != null && Source.JAVAX_API == source) + if (Source.JAVAX_API == source) { //programmatically defined filter mappings are prepended to mapping list in the order //in which they were defined. In other words, insert this mapping at the tail of the @@ -1281,7 +1246,7 @@ public class ServletHandler extends ScopedHandler // update the maps for (ServletHolder servlet : _servlets) { - _servletNameMap.put(servlet.getName(), servlet); + _servletNameMap.put(servlet.getName(), new MappedServlet(null, servlet)); servlet.setServletHandler(this); } } @@ -1321,13 +1286,13 @@ public class ServletHandler extends ScopedHandler } // Map servlet paths to holders - if (_servletMappings == null || _servletNameMap == null) + if (_servletMappings == null) { _servletPathMap = null; } else { - PathMappings pm = new PathMappings<>(); + PathMappings pm = new PathMappings<>(); //create a map of paths to set of ServletMappings that define that mapping HashMap> sms = new HashMap<>(); @@ -1338,12 +1303,7 @@ public class ServletHandler extends ScopedHandler { for (String pathSpec : pathSpecs) { - List mappings = sms.get(pathSpec); - if (mappings == null) - { - mappings = new ArrayList<>(); - sms.put(pathSpec, mappings); - } + List mappings = sms.computeIfAbsent(pathSpec, k -> new ArrayList<>()); mappings.add(servletMapping); } } @@ -1360,7 +1320,7 @@ public class ServletHandler extends ScopedHandler for (ServletMapping mapping : mappings) { //Get servlet associated with the mapping and check it is enabled - ServletHolder servletHolder = _servletNameMap.get(mapping.getServletName()); + ServletHolder servletHolder = getServlet(mapping.getServletName()); if (servletHolder == null) throw new IllegalStateException("No such servlet: " + mapping.getServletName()); //if the servlet related to the mapping is not enabled, skip it from consideration @@ -1374,7 +1334,7 @@ public class ServletHandler extends ScopedHandler { //already have a candidate - only accept another one //if the candidate is a default, or we're allowing duplicate mappings - if (finalMapping.isDefault()) + if (finalMapping.isFromDefaultDescriptor()) finalMapping = mapping; else if (isAllowDuplicateMappings()) { @@ -1384,9 +1344,9 @@ public class ServletHandler extends ScopedHandler else { //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error - if (!mapping.isDefault()) + if (!mapping.isFromDefaultDescriptor()) { - ServletHolder finalMappedServlet = _servletNameMap.get(finalMapping.getServletName()); + ServletHolder finalMappedServlet = getServlet(finalMapping.getServletName()); throw new IllegalStateException("Multiple servlets map to path " + pathSpec + ": " + finalMappedServlet.getName() + "[mapped:" + finalMapping.getSource() + "]," + @@ -1403,22 +1363,21 @@ public class ServletHandler extends ScopedHandler pathSpec, finalMapping.getSource(), finalMapping.getServletName(), - _servletNameMap.get(finalMapping.getServletName()).getSource()); + getServlet(finalMapping.getServletName()).getSource()); - pm.put(new ServletPathSpec(pathSpec), _servletNameMap.get(finalMapping.getServletName())); + ServletPathSpec servletPathSpec = new ServletPathSpec(pathSpec); + MappedServlet mappedServlet = new MappedServlet(servletPathSpec, getServlet(finalMapping.getServletName())); + pm.put(servletPathSpec, mappedServlet); } _servletPathMap = pm; } // flush filter chain cache - if (_chainCache != null) + for (int i = _chainCache.length; i-- > 0; ) { - for (int i = _chainCache.length; i-- > 0; ) - { - if (_chainCache[i] != null) - _chainCache[i].clear(); - } + if (_chainCache[i] != null) + _chainCache[i].clear(); } if (LOG.isDebugEnabled()) @@ -1453,28 +1412,26 @@ public class ServletHandler extends ScopedHandler { if (_filters == null) return false; - boolean found = false; for (FilterHolder f : _filters) { if (f == holder) - found = true; + return true; } - return found; + return false; } protected synchronized boolean containsServletHolder(ServletHolder holder) { if (_servlets == null) return false; - boolean found = false; for (ServletHolder s : _servlets) { @SuppressWarnings("ReferenceEquality") boolean foundServletHolder = (s == holder); if (foundServletHolder) - found = true; + return true; } - return found; + return false; } /** @@ -1731,6 +1688,68 @@ public class ServletHandler extends ScopedHandler _contextHandler.destroyListener(listener); } + /** + * A mapping of a servlet by pathSpec or by name + */ + public static class MappedServlet + { + private final PathSpec _pathSpec; + private final ServletHolder _servletHolder; + private final ServletPathMapping _servletPathMapping; + + MappedServlet(PathSpec pathSpec, ServletHolder servletHolder) + { + _pathSpec = pathSpec; + _servletHolder = servletHolder; + + // Create the HttpServletMapping only once if possible. + if (pathSpec instanceof ServletPathSpec) + { + switch (pathSpec.getGroup()) + { + case EXACT: + case ROOT: + _servletPathMapping = new ServletPathMapping(_pathSpec, _servletHolder.getName(), _pathSpec.getPrefix()); + break; + default: + _servletPathMapping = null; + break; + } + } + else + { + _servletPathMapping = null; + } + } + + public PathSpec getPathSpec() + { + return _pathSpec; + } + + public ServletHolder getServletHolder() + { + return _servletHolder; + } + + public ServletPathMapping getServletPathMapping(String pathInContext) + { + if (_servletPathMapping != null) + return _servletPathMapping; + if (_pathSpec != null) + return new ServletPathMapping(_pathSpec, _servletHolder.getName(), pathInContext); + return null; + } + + @Override + public String toString() + { + return String.format("MappedServlet%x{%s->%s}", + hashCode(), _pathSpec == null ? null : _pathSpec.getDeclaration(), _servletHolder); + } + + } + @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 ad185ff6488..4e42bb6f014 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 @@ -918,7 +918,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope if (mapping != null) { //if the servlet mapping was from a default descriptor, then allow it to be overridden - if (!mapping.isDefault()) + if (!mapping.isFromDefaultDescriptor()) { if (clash == null) clash = new HashSet<>(); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java index b4a9a2a68ae..8bd15c87492 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java @@ -104,12 +104,12 @@ public class ServletMapping } @ManagedAttribute(value = "default", readonly = true) - public boolean isDefault() + public boolean isFromDefaultDescriptor() { return _default; } - public void setDefault(boolean fromDefault) + public void setFromDefaultDescriptor(boolean fromDefault) { _default = fromDefault; } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java index 48cf1380151..2c50289dfd8 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java @@ -257,7 +257,7 @@ public class AsyncContextTest assertThat("async run attr query string", responseBody, containsString("async:run:attr:queryString:dispatch=true")); assertThat("async run context path", responseBody, containsString("async:run:attr:contextPath:/ctx")); assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/test/hello%2fthere")); - assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:/test")); + assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:test")); assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/test/*")); assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:")); assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:PATH")); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java index 8c886a54149..a6dcb6e8a9f 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -170,6 +170,43 @@ public class DispatcherTest assertEquals(expected, responses); } + @Test + public void testNamedForward() throws Exception + { + _contextHandler.addServlet(NamedForwardServlet.class, "/forward/*"); + String echo = _contextHandler.addServlet(EchoURIServlet.class, "/echo/*").getName(); + + String expected = + "HTTP/1.1 200 OK\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: 62\r\n" + + "\r\n" + + "/context\r\n" + + "/forward\r\n" + + "/info\r\n" + + "/context/forward/info;param=value\r\n"; + String responses = _connector.getResponse("GET /context/forward/info;param=value?name=" + echo + " HTTP/1.0\n\n"); + assertEquals(expected, responses); + } + + @Test + public void testNamedInclude() throws Exception + { + _contextHandler.addServlet(NamedIncludeServlet.class, "/include/*"); + String echo = _contextHandler.addServlet(EchoURIServlet.class, "/echo/*").getName(); + + String expected = + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 62\r\n" + + "\r\n" + + "/context\r\n" + + "/include\r\n" + + "/info\r\n" + + "/context/include/info;param=value\r\n"; + String responses = _connector.getResponse("GET /context/include/info;param=value?name=" + echo + " HTTP/1.0\n\n"); + assertEquals(expected, responses); + } + @Test public void testForwardWithBadParams() throws Exception { @@ -507,6 +544,24 @@ public class DispatcherTest } } + public static class NamedForwardServlet extends HttpServlet implements Servlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + getServletContext().getNamedDispatcher(request.getParameter("name")).forward(request, response); + } + } + + public static class NamedIncludeServlet extends HttpServlet implements Servlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + getServletContext().getNamedDispatcher(request.getParameter("name")).include(request, response); + } + } + public static class ForwardNonUTF8Servlet extends HttpServlet implements Servlet { @Override @@ -734,7 +789,7 @@ public class DispatcherTest assertEquals("do=assertforward&do=more&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING)); HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING); assertNotNull(fwdMapping); - assertEquals("/ForwardServlet", fwdMapping.getMatchValue()); + assertEquals("ForwardServlet", fwdMapping.getMatchValue()); List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING); @@ -769,7 +824,7 @@ public class DispatcherTest assertEquals("do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING)); HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING); assertNotNull(fwdMapping); - assertEquals("/ForwardServlet", fwdMapping.getMatchValue()); + assertEquals("ForwardServlet", fwdMapping.getMatchValue()); List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING); @@ -818,7 +873,7 @@ public class DispatcherTest assertEquals("do=end&do=the", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING)); HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING); assertNotNull(incMapping); - assertEquals("/AssertIncludeServlet", incMapping.getMatchValue()); + assertEquals("AssertIncludeServlet", incMapping.getMatchValue()); List expectedAttributeNames = Arrays.asList(Dispatcher.INCLUDE_REQUEST_URI, Dispatcher.INCLUDE_CONTEXT_PATH, Dispatcher.INCLUDE_SERVLET_PATH, Dispatcher.INCLUDE_QUERY_STRING, Dispatcher.INCLUDE_MAPPING); @@ -851,7 +906,7 @@ public class DispatcherTest assertEquals("do=include", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING)); HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING); assertNotNull(fwdMapping); - assertEquals("/ForwardServlet", fwdMapping.getMatchValue()); + assertEquals("ForwardServlet", fwdMapping.getMatchValue()); assertEquals("/context/AssertForwardIncludeServlet/assertpath", request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)); assertEquals("/context", request.getAttribute(Dispatcher.INCLUDE_CONTEXT_PATH)); @@ -860,7 +915,7 @@ public class DispatcherTest assertEquals("do=end", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING)); HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING); assertNotNull(incMapping); - assertEquals("/AssertForwardIncludeServlet", incMapping.getMatchValue()); + assertEquals("AssertForwardIncludeServlet", incMapping.getMatchValue()); List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING, @@ -902,7 +957,7 @@ public class DispatcherTest assertEquals("do=forward", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING)); HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING); assertNotNull(fwdMapping); - assertEquals("/IncludeServlet", fwdMapping.getMatchValue()); + assertEquals("IncludeServlet", fwdMapping.getMatchValue()); List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING); 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 bef8ceef9a4..643fd6bc65c 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 @@ -62,7 +62,6 @@ import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionIdListener; import javax.servlet.http.HttpSessionListener; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.RoleInfo; @@ -1376,9 +1375,9 @@ public class ServletContextHandlerTest root.addBean(new MySCIStarter(root.getServletContext(), new JSPAddingSCI()), true); _server.start(); - MappedResource mappedServlet = root.getServletHandler().getMappedServlet("/somejsp/xxx"); - assertNotNull(mappedServlet.getResource()); - assertEquals("some.jsp", mappedServlet.getResource().getName()); + ServletHandler.MappedServlet mappedServlet = root.getServletHandler().getMappedServlet("/somejsp/xxx"); + assertNotNull(mappedServlet.getServletHolder()); + assertEquals("some.jsp", mappedServlet.getServletHolder().getName()); } @Test @@ -1452,9 +1451,9 @@ public class ServletContextHandlerTest root.addBean(new MySCIStarter(root.getServletContext(), new JSPAddingSCI()), true); _server.start(); - MappedResource mappedServlet = root.getServletHandler().getMappedServlet("/bar/xxx"); - assertNotNull(mappedServlet.getResource()); - assertEquals("some.jsp", mappedServlet.getResource().getName()); + ServletHandler.MappedServlet mappedServlet = root.getServletHandler().getMappedServlet("/bar/xxx"); + assertNotNull(mappedServlet.getServletHolder()); + assertEquals("some.jsp", mappedServlet.getServletHolder().getName()); } @Test diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java index fa5736a2619..25d635f66c6 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.servlet; import java.util.EnumSet; import javax.servlet.DispatcherType; -import org.eclipse.jetty.http.pathmap.MappedResource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -79,17 +78,17 @@ public class ServletHandlerTest fm5.setFilterHolder(fh5); sh1.setName("s1"); - sm1.setDefault(false); + sm1.setFromDefaultDescriptor(false); sm1.setPathSpec("/foo/*"); sm1.setServletName("s1"); sh2.setName("s2"); - sm2.setDefault(false); + sm2.setFromDefaultDescriptor(false); sm2.setPathSpec("/foo/*"); sm2.setServletName("s2"); sh3.setName("s3"); - sm3.setDefault(true); + sm3.setFromDefaultDescriptor(true); sm3.setPathSpec("/foo/*"); sm3.setServletName("s3"); } @@ -251,9 +250,9 @@ public class ServletHandlerTest handler.updateMappings(); - MappedResource entry = handler.getMappedServlet("/foo/*"); + ServletHandler.MappedServlet entry = handler.getMappedServlet("/foo/*"); assertNotNull(entry); - assertEquals("s1", entry.getResource().getName()); + assertEquals("s1", entry.getServletHolder().getName()); } @Test @@ -292,9 +291,9 @@ public class ServletHandlerTest handler.addServletMapping(sm2); handler.updateMappings(); - MappedResource entry = handler.getMappedServlet("/foo/*"); + ServletHandler.MappedServlet entry = handler.getMappedServlet("/foo/*"); assertNotNull(entry); - assertEquals("s2", entry.getResource().getName()); + assertEquals("s2", entry.getServletHolder().getName()); } @Test diff --git a/jetty-slf4j-impl/pom.xml b/jetty-slf4j-impl/pom.xml index be399ee79a2..61d8e2fb1ce 100644 --- a/jetty-slf4j-impl/pom.xml +++ b/jetty-slf4j-impl/pom.xml @@ -4,16 +4,35 @@ jetty-project 10.0.0-SNAPSHOT + 4.0.0 jetty-slf4j-impl Jetty :: Slf4j Implementation Slf4j Logging Implementation based on Jetty's older StdErrLog - http://www.eclipse.org/jetty + ${project.groupId}.logging + + + maven-compiler-plugin + + + --add-modules + java.management + + + + + maven-surefire-plugin + + + @{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.logging=java.management + + + org.apache.felix maven-bundle-plugin @@ -29,15 +48,17 @@ + + + org.slf4j + slf4j-api + + org.eclipse.jetty.toolchain jetty-test-helper test - - org.slf4j - slf4j-api - diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLevel.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLevel.java new file mode 100644 index 00000000000..038ca466a99 --- /dev/null +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLevel.java @@ -0,0 +1,114 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.logging; + +import java.util.Locale; + +import org.slf4j.event.Level; + +public enum JettyLevel +{ + // Intentionally sorted incrementally by level int + ALL(Level.TRACE.toInt() - 10), + TRACE(Level.TRACE), + DEBUG(Level.DEBUG), + INFO(Level.INFO), + WARN(Level.WARN), + ERROR(Level.ERROR), + OFF(Level.ERROR.toInt() + 1); + + private final Level level; + private final int levelInt; + + JettyLevel(Level level) + { + this.level = level; + this.levelInt = level.toInt(); + } + + JettyLevel(int i) + { + this.level = null; + this.levelInt = i; + } + + public static JettyLevel fromLevel(Level slf4jLevel) + { + for (JettyLevel level : JettyLevel.values()) + { + if (slf4jLevel.toInt() == level.levelInt) + return level; + } + return OFF; + } + + public int toInt() + { + return levelInt; + } + + public Level toLevel() + { + return level; + } + + /** + * Tests that a provided level is included by the level value of this level. + * + * @param testLevel the level to test against. + * @return true if includes this includes the test level. + */ + public boolean includes(JettyLevel testLevel) + { + return (this.levelInt <= testLevel.levelInt); + } + + @Override + public String toString() + { + return name(); + } + + public static JettyLevel intToLevel(int levelInt) + { + for (JettyLevel level : JettyLevel.values()) + { + if (levelInt <= level.levelInt) + return level; + } + return OFF; + } + + public static JettyLevel strToLevel(String levelStr) + { + if (levelStr == null) + { + return null; + } + + String levelName = levelStr.trim().toUpperCase(Locale.ENGLISH); + for (JettyLevel level : JettyLevel.values()) + { + if (level.name().equals(levelName)) + return level; + } + + return null; + } +} diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java index 90e1a639b5b..08c65e7a7a0 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.logging; -import java.util.Objects; - import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.event.Level; @@ -29,37 +27,130 @@ import org.slf4j.spi.LocationAwareLogger; public class JettyLogger implements LocationAwareLogger, Logger { - /** - * The Level to set if you want this logger to be "OFF" - */ - public static final int OFF = 999; - /** - * The Level to set if you want this logger to show all events from all levels. - */ - public static final int ALL = -1; - private final JettyLoggerFactory factory; private final String name; private final String condensedName; private final JettyAppender appender; - private int level; - private boolean hideStacks = false; + private JettyLevel level; + private boolean hideStacks; public JettyLogger(JettyLoggerFactory factory, String name, JettyAppender appender) { - this(factory, name, appender, Level.INFO.toInt(), false); + this(factory, name, appender, JettyLevel.INFO, false); } - public JettyLogger(JettyLoggerFactory factory, String name, JettyAppender appender, int level, boolean hideStacks) + public JettyLogger(JettyLoggerFactory factory, String name, JettyAppender appender, JettyLevel level, boolean hideStacks) { this.factory = factory; this.name = name; - this.condensedName = JettyLoggerFactory.condensePackageString(name); + this.condensedName = condensePackageString(name); this.appender = appender; this.level = level; this.hideStacks = hideStacks; } + /** + * Condenses a classname by stripping down the package name to just the first character of each package name + * segment.Configured + * + *
+     * Examples:
+     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
+     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
+     * 
+ * + * @param classname the fully qualified class name + * @return the condensed name + */ + private static String condensePackageString(String classname) + { + if (classname == null || classname.isEmpty()) + return ""; + + int rawLen = classname.length(); + StringBuilder dense = new StringBuilder(rawLen); + boolean foundStart = false; + boolean hasPackage = false; + int startIdx = -1; + int endIdx = -1; + for (int i = 0; i < rawLen; i++) + { + char c = classname.charAt(i); + if (!foundStart) + { + foundStart = Character.isJavaIdentifierStart(c); + if (foundStart) + { + if (startIdx >= 0) + { + dense.append(classname.charAt(startIdx)); + hasPackage = true; + } + startIdx = i; + } + } + + if (foundStart) + { + if (Character.isJavaIdentifierPart(c)) + endIdx = i; + else + foundStart = false; + } + } + // append remaining from startIdx + if ((startIdx >= 0) && (endIdx >= startIdx)) + { + if (hasPackage) + dense.append('.'); + dense.append(classname, startIdx, endIdx + 1); + } + + return dense.toString(); + } + + public JettyAppender getAppender() + { + return appender; + } + + String getCondensedName() + { + return condensedName; + } + + public JettyLevel getLevel() + { + return level; + } + + public void setLevel(JettyLevel level) + { + this.level = level; + + // apply setLevel to children too. + factory.walkChildrenLoggers(this.getName(), (logger) -> logger.setLevel(level)); + } + + @Override + public String getName() + { + return name; + } + + public boolean isHideStacks() + { + return hideStacks; + } + + public void setHideStacks(boolean hideStacks) + { + this.hideStacks = hideStacks; + + // apply setHideStacks to children too. + factory.walkChildrenLoggers(this.getName(), (logger) -> logger.setHideStacks(hideStacks)); + } + @Override public void debug(String msg) { @@ -105,12 +196,6 @@ public class JettyLogger implements LocationAwareLogger, Logger } } - @Override - public boolean isDebugEnabled(Marker marker) - { - return isDebugEnabled(); - } - @Override public void debug(Marker marker, String msg) { @@ -146,6 +231,18 @@ public class JettyLogger implements LocationAwareLogger, Logger debug(msg, t); } + @Override + public boolean isDebugEnabled() + { + return level.includes(JettyLevel.DEBUG); + } + + @Override + public boolean isDebugEnabled(Marker marker) + { + return isDebugEnabled(); + } + @Override public void error(String msg) { @@ -191,12 +288,6 @@ public class JettyLogger implements LocationAwareLogger, Logger } } - @Override - public boolean isErrorEnabled(Marker marker) - { - return isErrorEnabled(); - } - @Override public void error(Marker marker, String msg) { @@ -232,66 +323,16 @@ public class JettyLogger implements LocationAwareLogger, Logger error(msg, t); } - public JettyAppender getAppender() - { - return appender; - } - - /** - * Entry point for {@link LocationAwareLogger} - */ @Override - public void log(Marker marker, String fqcn, int levelInt, String message, Object[] argArray, Throwable throwable) + public boolean isErrorEnabled() { - if (this.level <= levelInt) - { - long timestamp = System.currentTimeMillis(); - String threadName = Thread.currentThread().getName(); - getAppender().emit(this, intToLevel(levelInt), timestamp, threadName, throwable, message, argArray); - } - } - - /** - * Dynamic (via Reflection) entry point for {@link SubstituteLogger} usage. - * - * @param event the logging event - */ - @SuppressWarnings("unused") - public void log(LoggingEvent event) - { - // TODO: do we want to support org.sfl4j.Marker? - // TODO: do we want to support org.sfl4j.even.KeyValuePair? - getAppender().emit(this, event.getLevel(), event.getTimeStamp(), event.getThreadName(), event.getThrowable(), event.getMessage(), event.getArgumentArray()); - } - - public String getCondensedName() - { - return condensedName; - } - - public int getLevel() - { - return level; - } - - public void setLevel(Level level) - { - Objects.requireNonNull(level, "Level"); - setLevel(level.toInt()); - } - - public void setLevel(int lvlInt) - { - this.level = lvlInt; - - // apply setLevel to children too. - factory.walkChildLoggers(this.getName(), (logger) -> logger.setLevel(lvlInt)); + return level.includes(JettyLevel.ERROR); } @Override - public String getName() + public boolean isErrorEnabled(Marker marker) { - return name; + return isErrorEnabled(); } @Override @@ -339,12 +380,6 @@ public class JettyLogger implements LocationAwareLogger, Logger } } - @Override - public boolean isInfoEnabled(Marker marker) - { - return isInfoEnabled(); - } - @Override public void info(Marker marker, String msg) { @@ -380,47 +415,16 @@ public class JettyLogger implements LocationAwareLogger, Logger info(msg, t); } - @Override - public boolean isDebugEnabled() - { - return level <= Level.DEBUG.toInt(); - } - - @Override - public boolean isErrorEnabled() - { - return level <= Level.ERROR.toInt(); - } - - public boolean isHideStacks() - { - return hideStacks; - } - - public void setHideStacks(boolean hideStacks) - { - this.hideStacks = hideStacks; - - // apply setHideStacks to children too. - factory.walkChildLoggers(this.getName(), (logger) -> logger.setHideStacks(hideStacks)); - } - @Override public boolean isInfoEnabled() { - return level <= Level.INFO.toInt(); + return level.includes(JettyLevel.INFO); } @Override - public boolean isTraceEnabled() + public boolean isInfoEnabled(Marker marker) { - return level <= Level.TRACE.toInt(); - } - - @Override - public boolean isWarnEnabled() - { - return level <= Level.WARN.toInt(); + return isInfoEnabled(); } @Override @@ -468,12 +472,6 @@ public class JettyLogger implements LocationAwareLogger, Logger } } - @Override - public boolean isTraceEnabled(Marker marker) - { - return isTraceEnabled(); - } - @Override public void trace(Marker marker, String msg) { @@ -509,6 +507,18 @@ public class JettyLogger implements LocationAwareLogger, Logger trace(msg, t); } + @Override + public boolean isTraceEnabled() + { + return level.includes(JettyLevel.TRACE); + } + + @Override + public boolean isTraceEnabled(Marker marker) + { + return isTraceEnabled(); + } + @Override public void warn(String msg) { @@ -554,12 +564,6 @@ public class JettyLogger implements LocationAwareLogger, Logger } } - @Override - public boolean isWarnEnabled(Marker marker) - { - return isWarnEnabled(); - } - @Override public void warn(Marker marker, String msg) { @@ -595,6 +599,18 @@ public class JettyLogger implements LocationAwareLogger, Logger warn(msg, t); } + @Override + public boolean isWarnEnabled() + { + return level.includes(JettyLevel.WARN); + } + + @Override + public boolean isWarnEnabled(Marker marker) + { + return isWarnEnabled(); + } + private void emit(Level level, String msg) { long timestamp = System.currentTimeMillis(); @@ -636,43 +652,36 @@ public class JettyLogger implements LocationAwareLogger, Logger getAppender().emit(this, level, timestamp, threadName, throwable, msg); } - public static Level intToLevel(int level) + /** + * Entry point for {@link LocationAwareLogger} + */ + @Override + public void log(Marker marker, String fqcn, int levelInt, String message, Object[] argArray, Throwable throwable) { - if (level >= JettyLogger.OFF) - return Level.ERROR; - if (level >= Level.ERROR.toInt()) - return Level.ERROR; - if (level >= Level.WARN.toInt()) - return Level.WARN; - if (level >= Level.INFO.toInt()) - return Level.INFO; - if (level >= Level.DEBUG.toInt()) - return Level.DEBUG; - if (level >= Level.TRACE.toInt()) - return Level.TRACE; - return Level.TRACE; // everything else + if (this.level.toInt() <= levelInt) + { + long timestamp = System.currentTimeMillis(); + String threadName = Thread.currentThread().getName(); + getAppender().emit(this, JettyLevel.intToLevel(levelInt).toLevel(), timestamp, threadName, throwable, message, argArray); + } } - public static String levelToString(int level) + /** + * Dynamic (via Reflection) entry point for {@link SubstituteLogger} usage. + * + * @param event the logging event + */ + @SuppressWarnings("unused") + public void log(LoggingEvent event) { - if (level >= JettyLogger.OFF) - return "OFF"; - if (level >= Level.ERROR.toInt()) - return "ERROR"; - if (level >= Level.WARN.toInt()) - return "WARN"; - if (level >= Level.INFO.toInt()) - return "INFO"; - if (level >= Level.DEBUG.toInt()) - return "DEBUG"; - if (level >= Level.TRACE.toInt()) - return "TRACE"; - return "OFF"; // everything else + // TODO: do we want to support org.sfl4j.Marker? + // TODO: do we want to support org.sfl4j.even.KeyValuePair? + getAppender().emit(this, event.getLevel(), event.getTimeStamp(), event.getThreadName(), event.getThrowable(), event.getMessage(), event.getArgumentArray()); } @Override public String toString() { - return String.format("%s:%s:LEVEL=%s", JettyLogger.class.getSimpleName(), name, levelToString(level)); + return String.format("%s:%s:LEVEL=%s", JettyLogger.class.getSimpleName(), name, level.name()); } } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java index 2864430fee2..27097b2bd2f 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java @@ -23,12 +23,10 @@ import java.io.InputStream; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Arrays; import java.util.Locale; import java.util.Properties; import java.util.TimeZone; -import java.util.function.Function; - -import org.slf4j.event.Level; /** * JettyLogger specific configuration: @@ -39,7 +37,7 @@ import org.slf4j.event.Level; */ public class JettyLoggerConfiguration { - private static final int DEFAULT_LEVEL = Level.INFO.toInt(); + private static final JettyLevel DEFAULT_LEVEL = JettyLevel.INFO; private static final boolean DEFAULT_HIDE_STACKS = false; private static final String SUFFIX_LEVEL = ".LEVEL"; private static final String SUFFIX_STACKS = ".STACKS"; @@ -78,76 +76,77 @@ public class JettyLoggerConfiguration // strip ".STACKS" suffix (if present) if (startName.endsWith(SUFFIX_STACKS)) - { startName = startName.substring(0, startName.length() - SUFFIX_STACKS.length()); - } - Boolean hideStacks = walkParentLoggerNames(startName, (key) -> + Boolean hideStacks = JettyLoggerFactory.walkParentLoggerNames(startName, key -> { String stacksBool = properties.getProperty(key + SUFFIX_STACKS); if (stacksBool != null) - { return Boolean.parseBoolean(stacksBool); - } return null; }); - if (hideStacks != null) - return hideStacks; - - return DEFAULT_HIDE_STACKS; + return hideStacks != null ? hideStacks : DEFAULT_HIDE_STACKS; } /** - * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to - * shortest. + *

Returns the Logging Level for the provided log name.

+ *

Uses the FQCN first, then each package segment from longest to shortest.

* * @param name the name to get log for * @return the logging level int */ - public int getLevel(String name) + public JettyLevel getLevel(String name) { if (properties.isEmpty()) return DEFAULT_LEVEL; String startName = name != null ? name : ""; - // strip trailing dot + // Strip trailing dot. while (startName.endsWith(".")) { startName = startName.substring(0, startName.length() - 1); } - // strip ".LEVEL" suffix (if present) + // Strip ".LEVEL" suffix (if present). if (startName.endsWith(SUFFIX_LEVEL)) - { startName = startName.substring(0, startName.length() - SUFFIX_LEVEL.length()); - } - Integer level = walkParentLoggerNames(startName, (key) -> + JettyLevel level = JettyLoggerFactory.walkParentLoggerNames(startName, key -> { String levelStr = properties.getProperty(key + SUFFIX_LEVEL); - if (levelStr != null) - { - return getLevelInt(key, levelStr); - } - return null; + return toJettyLevel(key, levelStr); }); if (level == null) { - // try legacy root logging config - String levelStr = properties.getProperty("log" + SUFFIX_LEVEL); - if (levelStr != null) - { - level = getLevelInt("log", levelStr); - } + // Try slf4j root logging config. + String levelStr = properties.getProperty(JettyLogger.ROOT_LOGGER_NAME + SUFFIX_LEVEL); + level = toJettyLevel(JettyLogger.ROOT_LOGGER_NAME, levelStr); } - if (level != null) - return level; + if (level == null) + { + // Try legacy root logging config. + String levelStr = properties.getProperty("log" + SUFFIX_LEVEL); + level = toJettyLevel("log", levelStr); + } - return DEFAULT_LEVEL; + return level != null ? level : DEFAULT_LEVEL; + } + + static JettyLevel toJettyLevel(String loggerName, String levelStr) + { + if (levelStr == null) + return null; + JettyLevel level = JettyLevel.strToLevel(levelStr); + if (level == null) + { + System.err.printf("Unknown JettyLogger/SLF4J Level [%s]=[%s], expecting only %s as values.%n", + loggerName, levelStr, Arrays.toString(JettyLevel.values())); + } + return level; } public TimeZone getTimeZone(String key) @@ -155,7 +154,6 @@ public class JettyLoggerConfiguration String zoneIdStr = properties.getProperty(key); if (zoneIdStr == null) return null; - return TimeZone.getTimeZone(zoneIdStr); } @@ -193,6 +191,11 @@ public class JettyLoggerConfiguration }); } + public String getString(String key, String defValue) + { + return properties.getProperty(key, defValue); + } + public boolean getBoolean(String key, boolean defValue) { String val = properties.getProperty(key, Boolean.toString(defValue)); @@ -203,9 +206,7 @@ public class JettyLoggerConfiguration { String val = properties.getProperty(key, Integer.toString(defValue)); if (val == null) - { return defValue; - } try { return Integer.parseInt(val); @@ -216,46 +217,12 @@ public class JettyLoggerConfiguration } } - private Integer getLevelInt(String levelSegment, String levelStr) - { - if (levelStr == null) - { - return null; - } - - String levelName = levelStr.trim().toUpperCase(Locale.ENGLISH); - switch (levelName) - { - case "ALL": - return JettyLogger.ALL; - case "TRACE": - return Level.TRACE.toInt(); - case "DEBUG": - return Level.DEBUG.toInt(); - case "INFO": - return Level.INFO.toInt(); - case "WARN": - return Level.WARN.toInt(); - case "ERROR": - return Level.ERROR.toInt(); - case "OFF": - return JettyLogger.OFF; - default: - System.err.println("Unknown JettyLogger/Slf4J Level [" + levelSegment + "]=[" + levelName + "], expecting only [ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF] as values."); - return null; - } - } - private URL getResource(ClassLoader loader, String resourceName) { if (loader == null) - { return ClassLoader.getSystemResource(resourceName); - } else - { return loader.getResource(resourceName); - } } /** @@ -286,9 +253,7 @@ public class JettyLoggerConfiguration { URL propsUrl = getResource(loader, resourceName); if (propsUrl == null) - { return null; - } try (InputStream in = propsUrl.openStream()) { @@ -303,30 +268,4 @@ public class JettyLoggerConfiguration } return null; } - - private T walkParentLoggerNames(String startName, Function nameFunction) - { - String nameSegment = startName; - - // Checking with FQCN first, then each package segment from longest to shortest. - while ((nameSegment != null) && (nameSegment.length() > 0)) - { - T ret = nameFunction.apply(nameSegment); - if (ret != null) - return ret; - - // Trim and try again. - int idx = nameSegment.lastIndexOf('.'); - if (idx >= 0) - { - nameSegment = nameSegment.substring(0, idx); - } - else - { - nameSegment = null; - } - } - - return null; - } } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java index 83d39cf71c1..aa0bda76539 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java @@ -19,19 +19,20 @@ package org.eclipse.jetty.logging; import java.util.Objects; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; +import java.util.function.Function; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; -public class JettyLoggerFactory implements ILoggerFactory +public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBean { - private static final String ROOT_LOGGER_NAME = ""; private final JettyLoggerConfiguration configuration; private final JettyLogger rootLogger; - private ConcurrentMap loggerMap; + private final ConcurrentMap loggerMap; public JettyLoggerFactory(JettyLoggerConfiguration config) { @@ -41,9 +42,16 @@ public class JettyLoggerFactory implements ILoggerFactory StdErrAppender appender = new StdErrAppender(configuration); - rootLogger = new JettyLogger(this, ROOT_LOGGER_NAME, appender); - loggerMap.put(ROOT_LOGGER_NAME, rootLogger); - rootLogger.setLevel(configuration.getLevel(ROOT_LOGGER_NAME)); + rootLogger = new JettyLogger(this, Logger.ROOT_LOGGER_NAME, appender); + loggerMap.put(Logger.ROOT_LOGGER_NAME, rootLogger); + rootLogger.setLevel(configuration.getLevel(Logger.ROOT_LOGGER_NAME)); + } + + @SuppressWarnings("unused") + public String jmxContext() + { + // Used to build the ObjectName. + return configuration.getString("org.eclipse.jetty.logging.jmx.context", null); } /** @@ -54,11 +62,8 @@ public class JettyLoggerFactory implements ILoggerFactory */ public JettyLogger getJettyLogger(String name) { - if (name.equals(ROOT_LOGGER_NAME)) - { + if (name.equals(Logger.ROOT_LOGGER_NAME)) return getRootLogger(); - } - return loggerMap.computeIfAbsent(name, this::createLogger); } @@ -74,109 +79,96 @@ public class JettyLoggerFactory implements ILoggerFactory return getJettyLogger(name); } - protected void walkChildLoggers(String parentName, Consumer childConsumer) + void walkChildrenLoggers(String parentName, Consumer childConsumer) { String prefix = parentName; if (parentName.length() > 0 && !prefix.endsWith(".")) - { prefix += "."; - } for (JettyLogger logger : loggerMap.values()) { + // Skip self. if (logger.getName().equals(parentName)) - { - // skip self continue; - } - // is child, and is not itself + // It is a child, and is not itself. if (logger.getName().startsWith(prefix)) - { childConsumer.accept(logger); - } } } - public JettyLogger getRootLogger() + JettyLogger getRootLogger() { return rootLogger; } private JettyLogger createLogger(String name) { - // or is that handled by slf4j itself? JettyAppender appender = rootLogger.getAppender(); - int level = this.configuration.getLevel(name); + JettyLevel level = this.configuration.getLevel(name); boolean hideStacks = this.configuration.getHideStacks(name); return new JettyLogger(this, name, appender, level, hideStacks); } - /** - * Condenses a classname by stripping down the package name to just the first character of each package name - * segment.Configured - * - *
-     * Examples:
-     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
-     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
-     * 
- * - * @param classname the fully qualified class name - * @return the condensed name - */ - protected static String condensePackageString(String classname) + static T walkParentLoggerNames(String startName, Function nameFunction) { - if (classname == null || classname.isEmpty()) + if (startName == null) + return null; + + // Checking with FQCN first, then each package segment from longest to shortest. + String nameSegment = startName; + while (nameSegment.length() > 0) { - return ""; + T ret = nameFunction.apply(nameSegment); + if (ret != null) + return ret; + + // Trim and try again. + int idx = nameSegment.lastIndexOf('.'); + if (idx >= 0) + nameSegment = nameSegment.substring(0, idx); + else + break; } - int rawLen = classname.length(); - StringBuilder dense = new StringBuilder(rawLen); - boolean foundStart = false; - boolean hasPackage = false; - int startIdx = -1; - int endIdx = -1; - for (int i = 0; i < rawLen; i++) - { - char c = classname.charAt(i); - if (!foundStart) - { - foundStart = Character.isJavaIdentifierStart(c); - if (foundStart) - { - if (startIdx >= 0) - { - dense.append(classname.charAt(startIdx)); - hasPackage = true; - } - startIdx = i; - } - } + return nameFunction.apply(Logger.ROOT_LOGGER_NAME); + } - if (foundStart) - { - if (!Character.isJavaIdentifierPart(c)) - { - foundStart = false; - } - else - { - endIdx = i; - } - } - } - // append remaining from startIdx - if ((startIdx >= 0) && (endIdx >= startIdx)) - { - if (hasPackage) - { - dense.append('.'); - } - dense.append(classname, startIdx, endIdx + 1); - } + @Override + public String[] getLoggerNames() + { + TreeSet names = new TreeSet<>(loggerMap.keySet()); + return names.toArray(new String[0]); + } - return dense.toString(); + @Override + public int getLoggerCount() + { + return loggerMap.size(); + } + + @Override + public String getLoggerLevel(String loggerName) + { + return walkParentLoggerNames(loggerName, key -> + { + JettyLogger logger = loggerMap.get(key); + if (logger != null) + return logger.getLevel().name(); + return null; + }); + } + + @Override + public boolean setLoggerLevel(String loggerName, String levelName) + { + JettyLevel level = JettyLoggerConfiguration.toJettyLevel(loggerName, levelName); + if (level == null) + { + return false; + } + JettyLogger jettyLogger = getJettyLogger(loggerName); + jettyLogger.setLevel(level); + return true; } } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java new file mode 100644 index 00000000000..304410df716 --- /dev/null +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java @@ -0,0 +1,30 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.logging; + +public interface JettyLoggerFactoryMBean +{ + int getLoggerCount(); + + String[] getLoggerNames(); + + boolean setLoggerLevel(String loggerName, String levelName); + + String getLoggerLevel(String loggerName); +} diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java new file mode 100644 index 00000000000..6719982f81c --- /dev/null +++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java @@ -0,0 +1,73 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.logging; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import javax.management.JMX; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JMXTest +{ + @Test + public void testJMX() throws Exception + { + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + + Properties props = new Properties(); + JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); + JettyLoggerFactory loggerFactory = new JettyLoggerFactory(config); + + ObjectName objectName = ObjectName.getInstance("org.eclipse.jetty.logging", "type", JettyLoggerFactory.class.getSimpleName().toLowerCase(Locale.ENGLISH)); + mbeanServer.registerMBean(loggerFactory, objectName); + + JettyLoggerFactoryMBean mbean = JMX.newMBeanProxy(mbeanServer, objectName, JettyLoggerFactoryMBean.class); + + // Only the root logger. + assertEquals(1, mbean.getLoggerCount()); + + JettyLogger child = loggerFactory.getJettyLogger("org.eclipse.jetty.logging"); + JettyLogger parent = loggerFactory.getJettyLogger("org.eclipse.jetty"); + assertEquals(3, mbean.getLoggerCount()); + + // Names are sorted. + List expected = new ArrayList<>(Arrays.asList(JettyLogger.ROOT_LOGGER_NAME, parent.getName(), child.getName())); + expected.sort(String::compareTo); + String[] loggerNames = mbean.getLoggerNames(); + assertEquals(expected, Arrays.asList(loggerNames)); + + // Setting the parent level should propagate to the children. + parent.setLevel(JettyLevel.DEBUG); + assertEquals(parent.getLevel().toString(), mbean.getLoggerLevel(child.getName())); + + // Setting the level via JMX affects the logger. + assertTrue(mbean.setLoggerLevel(child.getName(), "INFO")); + assertEquals(JettyLevel.INFO, child.getLevel()); + } +} diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerConfigurationTest.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerConfigurationTest.java index bc3982c0c64..cdbdd5ebede 100644 --- a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerConfigurationTest.java +++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerConfigurationTest.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.logging; import java.util.Properties; import org.junit.jupiter.api.Test; -import org.slf4j.event.Level; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -46,8 +45,8 @@ public class JettyLoggerConfigurationTest assertFalse(appender.isCondensedNames()); assertEquals(appender.getThreadPadding(), 10); - int level = config.getLevel("com.mortbay"); - assertEquals(Level.WARN.toInt(), level); + JettyLevel level = config.getLevel("com.mortbay"); + assertEquals(JettyLevel.WARN, level); boolean stacks = config.getHideStacks("com.mortbay.Foo"); assertFalse(stacks); @@ -59,8 +58,8 @@ public class JettyLoggerConfigurationTest Properties props = new Properties(); props.setProperty("com.mortbay.LEVEL", "WARN"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); - int level = config.getLevel("com.mortbay"); - assertEquals(Level.WARN.toInt(), level); + JettyLevel level = config.getLevel("com.mortbay"); + assertEquals(JettyLevel.WARN, level); } @Test @@ -70,8 +69,8 @@ public class JettyLoggerConfigurationTest props.setProperty("com.mortbay.LEVEL", "WARN"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // extra trailing dot "." - int level = config.getLevel("com.mortbay."); - assertEquals(Level.WARN.toInt(), level); + JettyLevel level = config.getLevel("com.mortbay."); + assertEquals(JettyLevel.WARN, level); } @Test @@ -81,8 +80,8 @@ public class JettyLoggerConfigurationTest props.setProperty("com.mortbay.LEVEL", "WARN"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // asking for name with ".LEVEL" - int level = config.getLevel("com.mortbay.Bar.LEVEL"); - assertEquals(Level.WARN.toInt(), level); + JettyLevel level = config.getLevel("com.mortbay.Bar.LEVEL"); + assertEquals(JettyLevel.WARN, level); } @Test @@ -91,8 +90,8 @@ public class JettyLoggerConfigurationTest Properties props = new Properties(); props.setProperty("com.mortbay.LEVEL", "WARN"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); - int level = config.getLevel("com.mortbay.Foo"); - assertEquals(Level.WARN.toInt(), level); + JettyLevel level = config.getLevel("com.mortbay.Foo"); + assertEquals(JettyLevel.WARN, level); } @Test @@ -102,8 +101,8 @@ public class JettyLoggerConfigurationTest props.setProperty("com.mortbay.LEVEL", "WARN"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // asking for name that isn't configured, returns default value - int level = config.getLevel("org.eclipse.jetty"); - assertEquals(Level.INFO.toInt(), level); + JettyLevel level = config.getLevel("org.eclipse.jetty"); + assertEquals(JettyLevel.INFO, level); } @Test @@ -163,41 +162,41 @@ public class JettyLoggerConfigurationTest public void testGetLoggingLevelBad() { Properties props = new Properties(); - props.setProperty("log.LEVEL", "WARN"); + props.setProperty("ROOT.LEVEL", "WARN"); props.setProperty("org.eclipse.jetty.bad.LEVEL", "EXPECTED_BAD_LEVEL"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Level (because of bad level value) - assertEquals(Level.WARN.toInt(), config.getLevel("org.eclipse.jetty.bad")); + assertEquals(JettyLevel.WARN, config.getLevel("org.eclipse.jetty.bad")); } @Test public void testGetLoggingLevelLowercase() { Properties props = new Properties(); - props.setProperty("log.LEVEL", "warn"); + props.setProperty("ROOT.LEVEL", "warn"); props.setProperty("org.eclipse.jetty.util.LEVEL", "info"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Level - assertEquals(Level.WARN.toInt(), config.getLevel("org.eclipse.jetty")); + assertEquals(JettyLevel.WARN, config.getLevel("org.eclipse.jetty")); // Specific Level - assertEquals(Level.INFO.toInt(), config.getLevel("org.eclipse.jetty.util")); + assertEquals(JettyLevel.INFO, config.getLevel("org.eclipse.jetty.util")); } @Test public void testGetLoggingLevelRoot() { Properties props = new Properties(); - props.setProperty("log.LEVEL", "DEBUG"); + props.setProperty("ROOT.LEVEL", "DEBUG"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Levels - assertEquals(Level.DEBUG.toInt(), config.getLevel(null)); - assertEquals(Level.DEBUG.toInt(), config.getLevel("")); - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty")); + assertEquals(JettyLevel.DEBUG, config.getLevel(null)); + assertEquals(JettyLevel.DEBUG, config.getLevel("")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty")); String name = JettyLoggerConfigurationTest.class.getName(); - assertEquals(Level.DEBUG.toInt(), config.getLevel(name), "Default Logging Level - " + name + " name"); + assertEquals(JettyLevel.DEBUG, config.getLevel(name), "Default Logging Level - " + name + " name"); } @Test @@ -209,12 +208,12 @@ public class JettyLoggerConfigurationTest JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Levels - assertEquals(Level.INFO.toInt(), config.getLevel(null)); - assertEquals(Level.INFO.toInt(), config.getLevel("")); - assertEquals(Level.INFO.toInt(), config.getLevel("org.eclipse.jetty")); + assertEquals(JettyLevel.INFO, config.getLevel(null)); + assertEquals(JettyLevel.INFO, config.getLevel("")); + assertEquals(JettyLevel.INFO, config.getLevel("org.eclipse.jetty")); // Specified Level - assertEquals(JettyLogger.ALL, config.getLevel(name)); + assertEquals(JettyLevel.ALL, config.getLevel(name)); } @Test @@ -225,40 +224,40 @@ public class JettyLoggerConfigurationTest JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Levels - assertEquals(Level.INFO.toInt(), config.getLevel(null)); - assertEquals(Level.INFO.toInt(), config.getLevel("")); - assertEquals(Level.INFO.toInt(), config.getLevel("org.eclipse.jetty")); - assertEquals(Level.INFO.toInt(), config.getLevel("org.eclipse.jetty.server.BogusObject")); - assertEquals(Level.INFO.toInt(), config.getLevel(JettyLoggerConfigurationTest.class.getName())); + assertEquals(JettyLevel.INFO, config.getLevel(null)); + assertEquals(JettyLevel.INFO, config.getLevel("")); + assertEquals(JettyLevel.INFO, config.getLevel("org.eclipse.jetty")); + assertEquals(JettyLevel.INFO, config.getLevel("org.eclipse.jetty.server.BogusObject")); + assertEquals(JettyLevel.INFO, config.getLevel(JettyLoggerConfigurationTest.class.getName())); // Configured Level - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty.util.Bogus")); - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty.util")); - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty.util.resource.PathResource")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty.util.Bogus")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty.util")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty.util.resource.PathResource")); } @Test public void testGetLoggingLevelMixedLevels() { Properties props = new Properties(); - props.setProperty("log.LEVEL", "DEBUG"); + props.setProperty("ROOT.LEVEL", "DEBUG"); props.setProperty("org.eclipse.jetty.util.LEVEL", "WARN"); props.setProperty("org.eclipse.jetty.util.ConcurrentHashMap.LEVEL", "ALL"); JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); // Default Levels - assertEquals(Level.DEBUG.toInt(), config.getLevel(null)); - assertEquals(Level.DEBUG.toInt(), config.getLevel("")); - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty")); - assertEquals(Level.DEBUG.toInt(), config.getLevel("org.eclipse.jetty.server.BogusObject")); - assertEquals(Level.DEBUG.toInt(), config.getLevel(JettyLoggerConfigurationTest.class.getName())); + assertEquals(JettyLevel.DEBUG, config.getLevel(null)); + assertEquals(JettyLevel.DEBUG, config.getLevel("")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty")); + assertEquals(JettyLevel.DEBUG, config.getLevel("org.eclipse.jetty.server.BogusObject")); + assertEquals(JettyLevel.DEBUG, config.getLevel(JettyLoggerConfigurationTest.class.getName())); // Configured Level - assertEquals(Level.WARN.toInt(), config.getLevel("org.eclipse.jetty.util.MagicUtil")); - assertEquals(Level.WARN.toInt(), config.getLevel("org.eclipse.jetty.util")); - assertEquals(Level.WARN.toInt(), config.getLevel("org.eclipse.jetty.util.resource.PathResource")); + assertEquals(JettyLevel.WARN, config.getLevel("org.eclipse.jetty.util.MagicUtil")); + assertEquals(JettyLevel.WARN, config.getLevel("org.eclipse.jetty.util")); + assertEquals(JettyLevel.WARN, config.getLevel("org.eclipse.jetty.util.resource.PathResource")); - assertEquals(JettyLogger.ALL, config.getLevel("org.eclipse.jetty.util.ConcurrentHashMap")); + assertEquals(JettyLevel.ALL, config.getLevel("org.eclipse.jetty.util.ConcurrentHashMap")); } } diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java index 86b6fd6db8d..02a0167f45c 100644 --- a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java +++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java @@ -91,11 +91,11 @@ public class JettyLoggerTest JettyLogger log = factory.getJettyLogger("xxx"); - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); log.debug("testing {} {}", "test", "debug"); log.info("testing {} {}", "test", "info"); log.warn("testing {} {}", "test", "warn"); - log.setLevel(Level.INFO); + log.setLevel(JettyLevel.INFO); log.debug("YOU SHOULD NOT SEE THIS!"); output.assertContains("DEBUG:xxx:tname: testing test debug"); @@ -177,7 +177,7 @@ public class JettyLoggerTest appender.setStream(output); JettyLogger log = factory.getJettyLogger(JettyLoggerTest.class.getName()); - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); String nullMsg = null; try (StacklessLogging ignored = new StacklessLogging(log)) @@ -234,11 +234,11 @@ public class JettyLoggerTest log.warn("See Me"); // Set to debug level - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); log.warn("Hear Me"); // Set to warn level - log.setLevel(Level.WARN); + log.setLevel(JettyLevel.WARN); log.warn("Cheer Me"); log.warn("", new Throwable("out of focus")); @@ -279,18 +279,18 @@ public class JettyLoggerTest log.info("I will not buy"); // Level Debug - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); log.info("this record"); // Level All - log.setLevel(Level.TRACE); + log.setLevel(JettyLevel.TRACE); log.info("it is scratched."); log.info("", new Throwable("out of focus")); log.info("shot issue", new Throwable("scene lost")); // Level Warn - log.setLevel(Level.WARN); + log.setLevel(JettyLevel.WARN); log.info("sorry?"); log.info("", new Throwable("on editing room floor")); @@ -326,7 +326,7 @@ public class JettyLoggerTest JettyLogger log = factory.getJettyLogger(JettyLoggerTest.class.getName()); try (StacklessLogging ignored = new StacklessLogging(log)) { - log.setLevel(Level.ERROR); + log.setLevel(JettyLevel.ERROR); // Various logging events log.debug("Squelch"); @@ -359,7 +359,7 @@ public class JettyLoggerTest try (StacklessLogging ignored = new StacklessLogging(log)) { - log.setLevel(JettyLogger.OFF); + log.setLevel(JettyLevel.OFF); // Various logging events log.debug("Squelch"); @@ -399,18 +399,18 @@ public class JettyLoggerTest log.debug("", new Throwable("on editing room floor")); // Level Debug - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); log.debug("my hovercraft is"); log.debug("", new Throwable("out of focus")); log.debug("shot issue", new Throwable("scene lost")); // Level All - log.setLevel(Level.TRACE); + log.setLevel(JettyLevel.TRACE); log.debug("full of eels."); // Level Warn - log.setLevel(Level.WARN); + log.setLevel(JettyLevel.WARN); log.debug("what?"); // Validate Output @@ -444,22 +444,22 @@ public class JettyLoggerTest try (StacklessLogging ignored = new StacklessLogging(log)) { - log.setLevel(Level.TRACE); + log.setLevel(JettyLevel.TRACE); assertThat("log.level(trace).isDebugEnabled", log.isDebugEnabled(), is(true)); - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); assertThat("log.level(debug).isDebugEnabled", log.isDebugEnabled(), is(true)); - log.setLevel(Level.INFO); + log.setLevel(JettyLevel.INFO); assertThat("log.level(info).isDebugEnabled", log.isDebugEnabled(), is(false)); - log.setLevel(Level.WARN); + log.setLevel(JettyLevel.WARN); assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false)); - log.setLevel(Level.ERROR); + log.setLevel(JettyLevel.ERROR); assertThat("log.level(error).isDebugEnabled", log.isDebugEnabled(), is(false)); - log.setLevel(JettyLogger.OFF); + log.setLevel(JettyLevel.OFF); assertThat("log.level(null).isDebugEnabled", log.isDebugEnabled(), is(false)); } } @@ -478,23 +478,23 @@ public class JettyLoggerTest try (StacklessLogging ignored = new StacklessLogging(log)) { - log.setLevel(Level.TRACE); - assertThat("log.level(trace).getLevel()", log.getLevel(), is(Level.TRACE.toInt())); + log.setLevel(JettyLevel.TRACE); + assertThat("log.level(trace).getLevel()", log.getLevel(), is(JettyLevel.TRACE)); - log.setLevel(Level.DEBUG); - assertThat("log.level(debug).getLevel()", log.getLevel(), is(Level.DEBUG.toInt())); + log.setLevel(JettyLevel.DEBUG); + assertThat("log.level(debug).getLevel()", log.getLevel(), is(JettyLevel.DEBUG)); - log.setLevel(Level.INFO); - assertThat("log.level(info).getLevel()", log.getLevel(), is(Level.INFO.toInt())); + log.setLevel(JettyLevel.INFO); + assertThat("log.level(info).getLevel()", log.getLevel(), is(JettyLevel.INFO)); - log.setLevel(Level.WARN); - assertThat("log.level(warn).getLevel()", log.getLevel(), is(Level.WARN.toInt())); + log.setLevel(JettyLevel.WARN); + assertThat("log.level(warn).getLevel()", log.getLevel(), is(JettyLevel.WARN)); - log.setLevel(Level.ERROR); - assertThat("log.level(error).getLevel()", log.getLevel(), is(Level.ERROR.toInt())); + log.setLevel(JettyLevel.ERROR); + assertThat("log.level(error).getLevel()", log.getLevel(), is(JettyLevel.ERROR)); - log.setLevel(888); - assertThat("log.level(888).getLevel()", log.getLevel(), is(888)); + log.setLevel(JettyLevel.OFF); + assertThat("log.level(off).getLevel()", log.getLevel(), is(JettyLevel.OFF)); } } @@ -510,22 +510,22 @@ public class JettyLoggerTest JettyLogger log = factory.getJettyLogger("xxx"); - log.setLevel(Level.TRACE); + log.setLevel(JettyLevel.TRACE); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=TRACE")); - log.setLevel(Level.DEBUG); + log.setLevel(JettyLevel.DEBUG); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=DEBUG")); - log.setLevel(Level.INFO); + log.setLevel(JettyLevel.INFO); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=INFO")); - log.setLevel(Level.WARN); + log.setLevel(JettyLevel.WARN); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=WARN")); - log.setLevel(Level.ERROR); + log.setLevel(JettyLevel.ERROR); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=ERROR")); - log.setLevel(JettyLogger.OFF); + log.setLevel(JettyLevel.OFF); assertThat("Logger.toString", log.toString(), is("JettyLogger:xxx:LEVEL=OFF")); } @@ -544,25 +544,25 @@ public class JettyLoggerTest appender.setStream(output); JettyLogger root = factory.getJettyLogger(""); - assertLevel(root, Level.INFO); // default + assertLevel(root, JettyLevel.INFO); // default JettyLogger log = factory.getJettyLogger("org.eclipse.jetty.util.Foo"); assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false)); - assertLevel(log, Level.WARN); // as configured + assertLevel(log, JettyLevel.WARN); // as configured // Boot stomp it all to debug - root.setLevel(Level.DEBUG); + root.setLevel(JettyLevel.DEBUG); assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(true)); - assertLevel(log, Level.DEBUG); // as stomped + assertLevel(log, JettyLevel.DEBUG); // as stomped // Restore configured - factory.walkChildLoggers(root.getName(), (logger) -> + factory.walkChildrenLoggers(root.getName(), (logger) -> { - int configuredLevel = config.getLevel(logger.getName()); + JettyLevel configuredLevel = config.getLevel(logger.getName()); logger.setLevel(configuredLevel); }); assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false)); - assertLevel(log, Level.WARN); // as configured + assertLevel(log, JettyLevel.WARN); // as configured } @Test @@ -606,9 +606,9 @@ public class JettyLoggerTest output.assertContains("\t|\t|java.lang.Exception: branch0"); } - private void assertLevel(JettyLogger log, Level expectedLevel) + private void assertLevel(JettyLogger log, JettyLevel expectedLevel) { assertThat("Log[" + log.getName() + "].level", - JettyLogger.levelToString(log.getLevel()), is(expectedLevel.toString())); + log.getLevel(), is(expectedLevel)); } } diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java new file mode 100644 index 00000000000..a16d28bb041 --- /dev/null +++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java @@ -0,0 +1,1389 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.util.ajax; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.util.ArrayTernaryTrie; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.Trie; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.util.ajax.JSON.Convertible; +import org.eclipse.jetty.util.ajax.JSON.Convertor; + +/** + *

A non-blocking JSON parser that can parse partial JSON strings.

+ *

Usage:

+ *
+ * AsyncJSON parser = new AsyncJSON.Factory().newAsyncJSON();
+ *
+ * // Feed the parser with partial JSON string content.
+ * parser.parse(chunk1);
+ * parser.parse(chunk2);
+ *
+ * // Tell the parser that the JSON string content
+ * // is terminated and get the JSON object back.
+ * Map<String, Object> object = parser.complete();
+ * 
+ *

After the call to {@link #complete()} the parser can be reused to parse + * another JSON string.

+ *

Custom objects can be created by specifying a {@code "class"} or + * {@code "x-class"} field:

+ *
+ * String json = """
+ * {
+ *   "x-class": "com.acme.Person",
+ *   "firstName": "John",
+ *   "lastName": "Doe",
+ *   "age": 42
+ * }
+ * """
+ *
+ * parser.parse(json);
+ * com.acme.Person person = parser.complete();
+ * 
+ *

Class {@code com.acme.Person} must either implement {@link Convertible}, + * or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.

+ */ +public class AsyncJSON +{ + /** + *

The factory that creates AsyncJSON instances.

+ *

The factory can be configured with custom {@link Convertor}s, + * and with cached strings that will not be allocated if they can + * be looked up from the cache.

+ */ + public static class Factory + { + private Trie cache; + private Map convertors; + private boolean detailedParseException; + + /** + * @return whether a parse failure should report the whole JSON string or just the last chunk + */ + public boolean isDetailedParseException() + { + return detailedParseException; + } + + /** + * @param detailedParseException whether a parse failure should report the whole JSON string or just the last chunk + */ + public void setDetailedParseException(boolean detailedParseException) + { + this.detailedParseException = detailedParseException; + } + + /** + * @param value the string to cache + * @return whether the value can be cached + */ + public boolean cache(String value) + { + if (cache == null) + cache = new ArrayTernaryTrie.Growing<>(false, 64, 64); + + CachedString cached = new CachedString(value); + if (cached.isCacheable()) + { + cache.put(cached.encoded, cached); + return true; + } + return false; + } + + /** + *

Attempts to return a cached string from the buffer bytes.

+ *

In case of a cache hit, the string is returned and the buffer + * position updated.

+ *

In case of cache miss, {@code null} is returned and the buffer + * position is left unaltered.

+ * + * @param buffer the buffer to lookup the string from + * @return a cached string or {@code null} + */ + protected String cached(ByteBuffer buffer) + { + if (cache != null) + { + CachedString result = cache.getBest(buffer, 0, buffer.remaining()); + if (result != null) + { + buffer.position(buffer.position() + result.encoded.length()); + return result.value; + } + } + return null; + } + + /** + * @return a new parser instance + */ + public AsyncJSON newAsyncJSON() + { + return new AsyncJSON(this); + } + + /** + *

Associates the given {@link Convertor} to the given class name.

+ * + * @param className the domain class name such as {@code com.acme.Person} + * @param convertor the {@link Convertor} that converts {@code Map} to domain objects + */ + public void putConvertor(String className, Convertor convertor) + { + if (convertors == null) + convertors = new ConcurrentHashMap<>(); + convertors.put(className, convertor); + } + + /** + *

Removes the {@link Convertor} associated with the given class name.

+ * + * @param className the class name associated with the {@link Convertor} + * @return the {@link Convertor} associated with the class name, or {@code null} + */ + public Convertor removeConvertor(String className) + { + if (convertors != null) + return convertors.remove(className); + return null; + } + + /** + *

Returns the {@link Convertor} associated with the given class name, if any.

+ * + * @param className the class name associated with the {@link Convertor} + * @return the {@link Convertor} associated with the class name, or {@code null} + */ + public Convertor getConvertor(String className) + { + return convertors == null ? null : convertors.get(className); + } + + private static class CachedString + { + private final String encoded; + private final String value; + + private CachedString(String value) + { + this.encoded = new JSON().toJSON(value); + this.value = value; + } + + private boolean isCacheable() + { + for (int i = encoded.length(); i-- > 0;) + { + char c = encoded.charAt(i); + if (c > 127) + return false; + } + return true; + } + } + } + + private static final Object UNSET = new Object(); + + private final FrameStack stack = new FrameStack(); + private final NumberBuilder numberBuilder = new NumberBuilder(); + private final Utf8StringBuilder stringBuilder = new Utf8StringBuilder(32); + private final Factory factory; + private List chunks; + + public AsyncJSON(Factory factory) + { + this.factory = factory; + } + + // Used by tests only. + boolean isEmpty() + { + return stack.isEmpty(); + } + + /** + *

Feeds the parser with the given bytes chunk.

+ * + * @param bytes the bytes to parse + * @return whether the JSON parsing was complete + * @throws IllegalArgumentException if the JSON is malformed + */ + public boolean parse(byte[] bytes) + { + return parse(bytes, 0, bytes.length); + } + + /** + *

Feeds the parser with the given bytes chunk.

+ * + * @param bytes the bytes to parse + * @param offset the offset to start parsing from + * @param length the number of bytes to parse + * @return whether the JSON parsing was complete + * @throws IllegalArgumentException if the JSON is malformed + */ + public boolean parse(byte[] bytes, int offset, int length) + { + return parse(ByteBuffer.wrap(bytes, offset, length)); + } + + /** + *

Feeds the parser with the given buffer chunk.

+ * + * @param buffer the buffer to parse + * @return whether the JSON parsing was complete + * @throws IllegalArgumentException if the JSON is malformed + */ + public boolean parse(ByteBuffer buffer) + { + try + { + if (factory.isDetailedParseException()) + { + if (chunks == null) + chunks = new ArrayList<>(); + ByteBuffer copy = buffer.isDirect() + ? ByteBuffer.allocateDirect(buffer.remaining()) + : ByteBuffer.allocate(buffer.remaining()); + copy.put(buffer).flip(); + chunks.add(copy); + buffer.flip(); + } + + if (stack.isEmpty()) + stack.push(State.COMPLETE, UNSET); + + while (true) + { + Frame frame = stack.peek(); + State state = frame.state; + switch (state) + { + case COMPLETE: + { + if (frame.value == UNSET) + { + if (parseAny(buffer)) + break; + return false; + } + else + { + while (buffer.hasRemaining()) + { + int position = buffer.position(); + byte peek = buffer.get(position); + if (isWhitespace(peek)) + buffer.position(position + 1); + else + throw newInvalidJSON(buffer, "invalid character after JSON data"); + } + return true; + } + } + case NULL: + { + if (parseNull(buffer)) + break; + return false; + } + case TRUE: + { + if (parseTrue(buffer)) + break; + return false; + } + case FALSE: + { + if (parseFalse(buffer)) + break; + return false; + } + case NUMBER: + { + if (parseNumber(buffer)) + break; + return false; + } + case STRING: + { + if (parseString(buffer)) + break; + return false; + } + case ESCAPE: + { + if (parseEscape(buffer)) + break; + return false; + } + case UNICODE: + { + if (parseUnicode(buffer)) + break; + return false; + } + case ARRAY: + { + if (parseArray(buffer)) + break; + return false; + } + case OBJECT: + { + if (parseObject(buffer)) + break; + return false; + } + case OBJECT_FIELD: + { + if (parseObjectField(buffer)) + break; + return false; + } + case OBJECT_FIELD_NAME: + { + if (parseObjectFieldName(buffer)) + break; + return false; + } + case OBJECT_FIELD_VALUE: + { + if (parseObjectFieldValue(buffer)) + break; + return false; + } + default: + { + throw new IllegalStateException("invalid state " + state); + } + } + } + } + catch (Throwable x) + { + reset(); + throw x; + } + } + + /** + *

Signals to the parser that the parse data is complete, and returns + * the object parsed from the JSON chunks passed to the {@code parse()} + * methods.

+ * + * @param the type the result is cast to + * @return the result of the JSON parsing + * @throws IllegalArgumentException if the JSON is malformed + * @throws IllegalStateException if the no JSON was passed to the {@code parse()} methods + */ + public R complete() + { + try + { + if (stack.isEmpty()) + throw new IllegalStateException("no JSON parsed"); + + while (true) + { + State state = stack.peek().state; + switch (state) + { + case NUMBER: + { + Number value = numberBuilder.value(); + stack.pop(); + stack.peek().value(value); + break; + } + case COMPLETE: + { + if (stack.peek().value == UNSET) + throw new IllegalStateException("invalid state " + state); + return (R)end(); + } + default: + { + throw newInvalidJSON(BufferUtil.EMPTY_BUFFER, "incomplete JSON"); + } + } + } + } + catch (Throwable x) + { + reset(); + throw x; + } + } + + /** + *

When a JSON { is encountered during parsing, + * this method is called to create a new {@code Map} instance.

+ *

Subclasses may override to return a custom {@code Map} instance.

+ * + * @param context the parsing context + * @return a {@code Map} instance + */ + protected Map newObject(Context context) + { + return new HashMap<>(); + } + + /** + *

When a JSON [ is encountered during parsing, + * this method is called to create a new {@code List} instance.

+ *

Subclasses may override to return a custom {@code List} instance.

+ * + * @param context the parsing context + * @return a {@code List} instance + */ + protected List newArray(Context context) + { + return new ArrayList<>(); + } + + private Object end() + { + Object result = stack.peek().value; + reset(); + return result; + } + + private void reset() + { + stack.clear(); + chunks = null; + } + + private boolean parseAny(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte peek = buffer.get(buffer.position()); + switch (peek) + { + case '[': + if (parseArray(buffer)) + return true; + break; + case '{': + if (parseObject(buffer)) + return true; + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (parseNumber(buffer)) + return true; + break; + case '"': + if (parseString(buffer)) + return true; + break; + case 'f': + if (parseFalse(buffer)) + return true; + break; + case 'n': + if (parseNull(buffer)) + return true; + break; + case 't': + if (parseTrue(buffer)) + return true; + break; + default: + if (isWhitespace(peek)) + { + buffer.get(); + break; + } + throw newInvalidJSON(buffer, "unrecognized JSON value"); + } + } + return false; + } + + private boolean parseNull(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + case 'n': + if (stack.peek().state != State.NULL) + { + stack.push(State.NULL, 0); + parseNullCharacter(buffer, 0); + break; + } + throw newInvalidJSON(buffer, "invalid 'null' literal"); + case 'u': + parseNullCharacter(buffer, 1); + break; + case 'l': + int index = (Integer)stack.peek().value; + if (index == 2 || index == 3) + parseNullCharacter(buffer, index); + else + throw newInvalidJSON(buffer, "invalid 'null' literal"); + if (index == 3) + { + stack.pop(); + stack.peek().value(null); + return true; + } + break; + default: + throw newInvalidJSON(buffer, "invalid 'null' literal"); + } + } + return false; + } + + private void parseNullCharacter(ByteBuffer buffer, int index) + { + Frame frame = stack.peek(); + int value = (Integer)frame.value; + if (value == index) + frame.value = ++value; + else + throw newInvalidJSON(buffer, "invalid 'null' literal"); + } + + private boolean parseTrue(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + case 't': + if (stack.peek().state != State.TRUE) + { + stack.push(State.TRUE, 0); + parseTrueCharacter(buffer, 0); + break; + } + throw newInvalidJSON(buffer, "invalid 'true' literal"); + case 'r': + parseTrueCharacter(buffer, 1); + break; + case 'u': + parseTrueCharacter(buffer, 2); + break; + case 'e': + parseTrueCharacter(buffer, 3); + stack.pop(); + stack.peek().value(Boolean.TRUE); + return true; + default: + throw newInvalidJSON(buffer, "invalid 'true' literal"); + } + } + return false; + } + + private void parseTrueCharacter(ByteBuffer buffer, int index) + { + Frame frame = stack.peek(); + int value = (Integer)frame.value; + if (value == index) + frame.value = ++value; + else + throw newInvalidJSON(buffer, "invalid 'true' literal"); + } + + private boolean parseFalse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + case 'f': + if (stack.peek().state != State.FALSE) + { + stack.push(State.FALSE, 0); + parseFalseCharacter(buffer, 0); + break; + } + throw newInvalidJSON(buffer, "invalid 'false' literal"); + case 'a': + parseFalseCharacter(buffer, 1); + break; + case 'l': + parseFalseCharacter(buffer, 2); + break; + case 's': + parseFalseCharacter(buffer, 3); + break; + case 'e': + parseFalseCharacter(buffer, 4); + stack.pop(); + stack.peek().value(Boolean.FALSE); + return true; + default: + throw newInvalidJSON(buffer, "invalid 'false' literal"); + } + } + return false; + } + + private void parseFalseCharacter(ByteBuffer buffer, int index) + { + Frame frame = stack.peek(); + int value = (Integer)frame.value; + if (value == index) + frame.value = ++value; + else + throw newInvalidJSON(buffer, "invalid 'false' literal"); + } + + private boolean parseNumber(ByteBuffer buffer) + { + if (stack.peek().state != State.NUMBER) + stack.push(State.NUMBER, numberBuilder); + + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + case '+': + case '-': + if (numberBuilder.appendSign(currentByte)) + break; + throw newInvalidJSON(buffer, "invalid number"); + case '.': + case 'E': + case 'e': + if (numberBuilder.appendAlpha(currentByte)) + break; + throw newInvalidJSON(buffer, "invalid number"); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + numberBuilder.appendDigit(currentByte); + break; + default: + buffer.position(buffer.position() - 1); + Number value = numberBuilder.value(); + stack.pop(); + stack.peek().value(value); + return true; + } + } + return false; + } + + private boolean parseString(ByteBuffer buffer) + { + Frame frame = stack.peek(); + if (buffer.hasRemaining() && frame.state != State.STRING) + { + String result = factory.cached(buffer); + if (result != null) + { + frame.value(result); + return true; + } + } + + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + // Explicit delimiter, handle push and pop in this method. + case '"': + { + if (stack.peek().state != State.STRING) + { + stack.push(State.STRING, stringBuilder); + break; + } + else + { + String string = stringBuilder.toString(); + stringBuilder.reset(); + stack.pop(); + stack.peek().value(string); + return true; + } + } + case '\\': + { + buffer.position(buffer.position() - 1); + if (parseEscape(buffer)) + break; + return false; + } + default: + { + stringBuilder.append(currentByte); + break; + } + } + } + return false; + } + + private boolean parseEscape(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + case '\\': + if (stack.peek().state != State.ESCAPE) + { + stack.push(State.ESCAPE, stringBuilder); + break; + } + else + { + return parseEscapeCharacter((char)currentByte); + } + case '"': + case '/': + return parseEscapeCharacter((char)currentByte); + case 'b': + return parseEscapeCharacter('\b'); + case 'f': + return parseEscapeCharacter('\f'); + case 'n': + return parseEscapeCharacter('\n'); + case 'r': + return parseEscapeCharacter('\r'); + case 't': + return parseEscapeCharacter('\t'); + case 'u': + stack.push(State.UNICODE, ByteBuffer.allocate(4)); + return parseUnicode(buffer); + default: + throw newInvalidJSON(buffer, "invalid escape sequence"); + } + } + return false; + } + + private boolean parseEscapeCharacter(char escape) + { + stack.pop(); + stringBuilder.append(escape); + return true; + } + + private boolean parseUnicode(ByteBuffer buffer) + { + // Expect 4 hex digits. + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + ByteBuffer hex = (ByteBuffer)stack.peek().value; + hex.put(hexToByte(buffer, currentByte)); + if (!hex.hasRemaining()) + { + int result = (hex.get(0) << 12) + + (hex.get(1) << 8) + + (hex.get(2) << 4) + + (hex.get(3)); + stack.pop(); + // Also done with escape parsing. + stack.pop(); + stringBuilder.append((char)result); + return true; + } + } + return false; + } + + private byte hexToByte(ByteBuffer buffer, byte currentByte) + { + try + { + return TypeUtil.convertHexDigit(currentByte); + } + catch (Throwable x) + { + throw newInvalidJSON(buffer, "invalid hex digit"); + } + } + + private boolean parseArray(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte peek = buffer.get(buffer.position()); + switch (peek) + { + // Explicit delimiters, handle push and pop in this method. + case '[': + { + buffer.get(); + stack.push(State.ARRAY, newArray(stack)); + break; + } + case ']': + { + buffer.get(); + Object array = stack.peek().value; + stack.pop(); + stack.peek().value(array); + return true; + } + case ',': + { + buffer.get(); + break; + } + default: + { + if (isWhitespace(peek)) + { + buffer.get(); + break; + } + else + { + if (parseAny(buffer)) + { + break; + } + return false; + } + } + } + } + return false; + } + + private boolean parseObject(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte currentByte = buffer.get(); + switch (currentByte) + { + // Explicit delimiters, handle push and pop in this method. + case '{': + { + if (stack.peek().state != State.OBJECT) + { + stack.push(State.OBJECT, newObject(stack)); + break; + } + throw newInvalidJSON(buffer, "invalid object"); + } + case '}': + { + @SuppressWarnings("unchecked") + Map object = (Map)stack.peek().value; + stack.pop(); + stack.peek().value(convertObject(object)); + return true; + } + case ',': + { + break; + } + default: + { + if (isWhitespace(currentByte)) + { + break; + } + else + { + buffer.position(buffer.position() - 1); + if (parseObjectField(buffer)) + break; + return false; + } + } + } + } + return false; + } + + private boolean parseObjectField(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte peek = buffer.get(buffer.position()); + switch (peek) + { + case '"': + { + if (stack.peek().state == State.OBJECT) + { + stack.push(State.OBJECT_FIELD, UNSET); + if (parseObjectFieldName(buffer)) + { + // We are not done yet, parse the value. + break; + } + return false; + } + else + { + return parseObjectFieldValue(buffer); + } + } + default: + { + if (isWhitespace(peek)) + { + buffer.get(); + break; + } + else if (stack.peek().state == State.OBJECT_FIELD_VALUE) + { + return parseObjectFieldValue(buffer); + } + else + { + throw newInvalidJSON(buffer, "invalid object field"); + } + } + } + } + return false; + } + + private boolean parseObjectFieldName(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + byte peek = buffer.get(buffer.position()); + switch (peek) + { + case '"': + { + if (stack.peek().state == State.OBJECT_FIELD) + { + stack.push(State.OBJECT_FIELD_NAME, UNSET); + if (parseString(buffer)) + { + // We are not done yet, parse until the ':'. + break; + } + return false; + } + else + { + throw newInvalidJSON(buffer, "invalid object field"); + } + } + case ':': + { + buffer.get(); + // We are done with the field name. + String fieldName = (String)stack.peek().value; + stack.pop(); + // Change state to parse the field value. + stack.push(fieldName, State.OBJECT_FIELD_VALUE, UNSET); + return true; + } + default: + { + if (isWhitespace(peek)) + { + buffer.get(); + break; + } + else + { + throw newInvalidJSON(buffer, "invalid object field"); + } + } + } + } + return false; + } + + private boolean parseObjectFieldValue(ByteBuffer buffer) + { + if (stack.peek().value == UNSET) + { + if (!parseAny(buffer)) + return false; + } + + // We are done with the field value. + Frame frame = stack.peek(); + Object value = frame.value; + String name = frame.name; + stack.pop(); + // We are done with the field. + stack.pop(); + @SuppressWarnings("unchecked") + Map map = (Map)stack.peek().value; + map.put(name, value); + + return true; + } + + private Object convertObject(Map object) + { + Object result = convertObject("x-class", object); + if (result == null) + { + result = convertObject("class", object); + if (result == null) + return object; + } + return result; + } + + private Object convertObject(String fieldName, Map object) + { + String className = (String)object.get(fieldName); + if (className == null) + return null; + + Convertible convertible = toConvertible(className); + if (convertible != null) + { + convertible.fromJSON(object); + return convertible; + } + + Convertor convertor = factory.getConvertor(className); + if (convertor != null) + return convertor.fromJSON(object); + + return null; + } + + private Convertible toConvertible(String className) + { + try + { + Class klass = Loader.loadClass(className); + if (Convertible.class.isAssignableFrom(klass)) + return (Convertible)klass.getConstructor().newInstance(); + return null; + } + catch (Throwable x) + { + throw new IllegalArgumentException(x); + } + } + + protected RuntimeException newInvalidJSON(ByteBuffer buffer, String message) + { + Utf8StringBuilder builder = new Utf8StringBuilder(); + builder.append(System.lineSeparator()); + int position = buffer.position(); + if (factory.isDetailedParseException()) + { + chunks.forEach(chunk -> builder.append(buffer)); + } + else + { + buffer.position(0); + builder.append(buffer); + buffer.position(position); + } + builder.append(System.lineSeparator()); + String indent = ""; + if (position > 1) + { + char[] chars = new char[position - 1]; + Arrays.fill(chars, ' '); + indent = new String(chars); + } + builder.append(indent); + builder.append("^ "); + builder.append(message); + return new IllegalArgumentException(builder.toString()); + } + + private static boolean isWhitespace(byte ws) + { + switch (ws) + { + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + + /** + *

The state of JSON parsing.

+ */ + public interface Context + { + /** + * @return the depth in the JSON structure + */ + public int depth(); + } + + private enum State + { + COMPLETE, NULL, TRUE, FALSE, NUMBER, STRING, ESCAPE, UNICODE, ARRAY, OBJECT, OBJECT_FIELD, OBJECT_FIELD_NAME, OBJECT_FIELD_VALUE + } + + private static class Frame + { + private String name; + private State state; + private Object value; + + private void value(Object value) + { + switch (state) + { + case COMPLETE: + case STRING: + case OBJECT_FIELD_NAME: + case OBJECT_FIELD_VALUE: + { + this.value = value; + break; + } + case ARRAY: + { + @SuppressWarnings("unchecked") + List array = (List)this.value; + array.add(value); + break; + } + default: + { + throw new IllegalStateException("invalid state " + state); + } + } + } + } + + private static class NumberBuilder + { + // 1 => positive integer + // 0 => non-integer + // -1 => negative integer + private int integer = 1; + private long value; + private StringBuilder builder; + + private boolean appendSign(byte b) + { + if (integer == 0) + { + if (builder.length() == 0) + { + builder.append((char)b); + return true; + } + else + { + char c = builder.charAt(builder.length() - 1); + if (c == 'E' || c == 'e') + { + builder.append((char)b); + return true; + } + } + return false; + } + else + { + if (value == 0) + { + if (b == '-') + { + if (integer == 1) + { + integer = -1; + return true; + } + } + else + { + return true; + } + } + } + return false; + } + + private void appendDigit(byte b) + { + if (integer == 0) + builder.append((char)b); + else + value = value * 10 + (b - '0'); + } + + private boolean appendAlpha(byte b) + { + if (integer == 0) + { + char c = builder.charAt(builder.length() - 1); + if ('0' <= c && c <= '9' && builder.indexOf("" + (char)b) < 0) + { + builder.append((char)b); + return true; + } + } + else + { + if (builder == null) + builder = new StringBuilder(16); + if (integer == -1) + builder.append('-'); + integer = 0; + builder.append(value); + builder.append((char)b); + return true; + } + return false; + } + + private Number value() + { + try + { + if (integer == 0) + return Double.parseDouble(builder.toString()); + return integer * value; + } + finally + { + reset(); + } + } + + private void reset() + { + integer = 1; + value = 0; + if (builder != null) + builder.setLength(0); + } + } + + private static class FrameStack implements AsyncJSON.Context + { + private final List stack = new ArrayList<>(); + private int cursor; + + private FrameStack() + { + grow(6); + } + + private void grow(int grow) + { + for (int i = 0; i < grow; i++) + { + stack.add(new Frame()); + } + } + + private void clear() + { + while (!isEmpty()) + { + pop(); + } + } + + private boolean isEmpty() + { + return cursor == 0; + } + + @Override + public int depth() + { + return cursor - 1; + } + + private Frame peek() + { + if (isEmpty()) + throw new IllegalStateException("empty stack"); + return stack.get(depth()); + } + + private void push(AsyncJSON.State state, Object value) + { + push(null, state, value); + } + + private void push(String name, AsyncJSON.State state, Object value) + { + if (cursor == stack.size()) + grow(2); + ++cursor; + Frame frame = stack.get(depth()); + frame.name = name; + frame.state = state; + frame.value = value; + } + + private void pop() + { + if (isEmpty()) + throw new IllegalStateException("empty stack"); + Frame frame = stack.get(depth()); + --cursor; + frame.name = null; + frame.value = null; + frame.state = null; + } + } +} diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java new file mode 100644 index 00000000000..c2de64fb282 --- /dev/null +++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/AsyncJSONTest.java @@ -0,0 +1,528 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.util.ajax; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AsyncJSONTest +{ + private static AsyncJSON newAsyncJSON() + { + AsyncJSON.Factory factory = new AsyncJSON.Factory(); + factory.setDetailedParseException(true); + return factory.newAsyncJSON(); + } + + @ParameterizedTest + @ValueSource(strings = {"|", "}", "]", "{]", "[}", "+", ".", "{} []"}) + public void testParseInvalidJSON(String json) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertThrows(IllegalArgumentException.class, () -> parser.parse(bytes)); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + assertThrows(IllegalArgumentException.class, () -> + { + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + }); + assertTrue(parser.isEmpty()); + } + + @ParameterizedTest(name = "[{index}] ''{0}'' -> ''{1}''") + @MethodSource("validStrings") + public void testParseString(String string, String expected) + { + String json = "\"${value}\"".replace("${value}", string); + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertTrue(parser.parse(bytes)); + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + for (int i = 0; i < bytes.length; ++i) + { + byte b = bytes[i]; + if (i == bytes.length - 1) + assertTrue(parser.parse(new byte[]{b})); + else + assertFalse(parser.parse(new byte[]{b})); + } + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + } + + public static List validStrings() + { + List result = new ArrayList<>(); + result.add(new Object[]{"", ""}); + result.add(new Object[]{" \t\r\n", " \t\r\n"}); + result.add(new Object[]{"\u20AC", "\u20AC"}); // euro symbol + result.add(new Object[]{"\\u20AC", "\u20AC"}); // euro symbol + result.add(new Object[]{"/foo", "/foo"}); + result.add(new Object[]{"123E+01", "123E+01"}); + result.add(new Object[]{"A\\u20AC/foo\\t\\n", "A\u20AC/foo\t\n"}); // euro symbol + result.add(new Object[]{" ABC ", " ABC "}); + return result; + } + + @ParameterizedTest + @ValueSource(strings = {"\\u", "\\u0", "\\x"}) + public void testParseInvalidString(String value) + { + String json = "\"${value}\"".replace("${value}", value); + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertThrows(IllegalArgumentException.class, () -> parser.parse(bytes)); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + assertThrows(IllegalArgumentException.class, () -> + { + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + }); + assertTrue(parser.isEmpty()); + } + + @ParameterizedTest(name = "[{index}] {0} -> {1}") + @MethodSource("validArrays") + public void testParseArray(String json, List expected) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertTrue(parser.parse(bytes)); + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + assertTrue(parser.parse(buffer)); + assertFalse(buffer.hasRemaining()); + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + } + + public static List validArrays() + { + List result = new ArrayList<>(); + + List expected = Collections.emptyList(); + result.add(new Object[]{"[]", expected}); + result.add(new Object[]{"[] \n", expected}); + + expected = new ArrayList<>(); + expected.add(Collections.emptyList()); + result.add(new Object[]{"[[]]", expected}); + + expected = new ArrayList<>(); + expected.add("first"); + expected.add(5D); + expected.add(null); + expected.add(true); + expected.add(false); + expected.add(new HashMap<>()); + HashMap last = new HashMap<>(); + last.put("a", new ArrayList<>()); + expected.add(last); + result.add(new Object[]{"[\"first\", 5E+0, null, true, false, {}, {\"a\":[]}]", expected}); + + return result; + } + + @ParameterizedTest + @ValueSource(strings = {"[", "]", "[[,]", " [ 1,2,[ "}) + public void testParseInvalidArray(String json) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertThrows(IllegalArgumentException.class, () -> + { + parser.parse(bytes); + parser.complete(); + }); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + assertThrows(IllegalArgumentException.class, () -> + { + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + parser.complete(); + }); + assertTrue(parser.isEmpty()); + } + + @ParameterizedTest(name = "[{index}] {0} -> {1}") + @MethodSource("validObjects") + public void testParseObject(String json, Object expected) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertTrue(parser.parse(bytes)); + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + for (int i = 0; i < bytes.length; ++i) + { + byte b = bytes[i]; + if (i == bytes.length - 1) + { + assertTrue(parser.parse(new byte[]{b})); + } + else + { + assertFalse(parser.parse(new byte[]{b})); + } + } + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + } + + public static List validObjects() + { + List result = new ArrayList<>(); + + HashMap expected = new HashMap<>(); + result.add(new Object[]{"{}", expected}); + + expected = new HashMap<>(); + expected.put("", 0L); + result.add(new Object[]{"{\"\":0}", expected}); + + expected = new HashMap<>(); + expected.put("name", "value"); + result.add(new Object[]{"{ \"name\": \"value\" }", expected}); + + expected = new HashMap<>(); + expected.put("name", null); + expected.put("valid", true); + expected.put("secure", false); + expected.put("value", 42L); + result.add(new Object[]{ + "{, \"name\": null, \"valid\": true\n , \"secure\": false\r\n,\n \"value\":42, }", expected + }); + + return result; + } + + @ParameterizedTest + @ValueSource(strings = {"{", "}", "{{,}", "{:\"s\"}", "{[]:0}", "{1:0}", " {\": 0} ", "{\"a: \"b\"}"}) + public void testParseInvalidObject(String json) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertThrows(IllegalArgumentException.class, () -> + { + parser.parse(bytes); + parser.complete(); + }); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + assertThrows(IllegalArgumentException.class, () -> + { + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + parser.complete(); + }); + assertTrue(parser.isEmpty()); + } + + @ParameterizedTest(name = "[{index}] {0} -> {1}") + @MethodSource("validNumbers") + public void testParseNumber(String json, Number expected) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + parser.parse(bytes); + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + parser.parse(buffer); + assertEquals(expected, parser.complete()); + assertFalse(buffer.hasRemaining()); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + assertEquals(expected, parser.complete()); + assertTrue(parser.isEmpty()); + } + + public static List validNumbers() + { + List result = new ArrayList<>(); + + result.add(new Object[]{"0", 0L}); + result.add(new Object[]{"-0", -0L}); + result.add(new Object[]{"13\n", 13L}); + result.add(new Object[]{"-42", -42L}); + result.add(new Object[]{"123.456", 123.456D}); + result.add(new Object[]{"-234.567", -234.567D}); + result.add(new Object[]{"9e0", 9D}); + result.add(new Object[]{"8E+1\t", 80D}); + result.add(new Object[]{"-7E-2 ", -0.07D}); + result.add(new Object[]{"70.5E-1", 7.05D}); + + return result; + } + + @ParameterizedTest + @ValueSource(strings = {"--", "--1", ".5", "e0", "1a1", "3-7", "1+2", "1e0e1", "1.2.3"}) + public void testParseInvalidNumber(String json) + { + byte[] bytes = json.getBytes(UTF_8); + AsyncJSON parser = newAsyncJSON(); + + // Parse the whole input. + assertThrows(IllegalArgumentException.class, () -> + { + parser.parse(bytes); + parser.complete(); + }); + assertTrue(parser.isEmpty()); + + // Parse byte by byte. + assertThrows(IllegalArgumentException.class, () -> + { + for (byte b : bytes) + { + parser.parse(new byte[]{b}); + } + parser.complete(); + }); + assertTrue(parser.isEmpty()); + } + + @Test + public void testParseObjectWithConvertor() + { + AsyncJSON.Factory factory = new AsyncJSON.Factory(); + CustomConvertor convertor = new CustomConvertor(); + factory.putConvertor(CustomConvertor.class.getName(), convertor); + + String json = "{" + + "\"f1\": {\"class\":\"" + CustomConvertible.class.getName() + "\", \"field\": \"value\"}," + + "\"f2\": {\"class\":\"" + CustomConvertor.class.getName() + "\"}" + + "}"; + + AsyncJSON parser = factory.newAsyncJSON(); + assertTrue(parser.parse(UTF_8.encode(json))); + Map result = parser.complete(); + + Object value1 = result.get("f1"); + assertTrue(value1 instanceof CustomConvertible); + assertEquals("value", ((CustomConvertible)value1).field); + Object value2 = result.get("f2"); + assertTrue(value2 instanceof CustomConvertor.Custom); + + assertSame(convertor, factory.removeConvertor(CustomConvertor.class.getName())); + assertTrue(parser.parse(UTF_8.encode(json))); + result = parser.complete(); + + value1 = result.get("f1"); + assertTrue(value1 instanceof CustomConvertible); + assertEquals("value", ((CustomConvertible)value1).field); + value2 = result.get("f2"); + assertTrue(value2 instanceof Map); + @SuppressWarnings("unchecked") + Map map2 = (Map)value2; + assertEquals(CustomConvertor.class.getName(), map2.get("class")); + } + + public static class CustomConvertible implements JSON.Convertible + { + private Object field; + + @Override + public void toJSON(JSON.Output out) + { + } + + @Override + public void fromJSON(Map map) + { + this.field = map.get("field"); + } + } + + public static class CustomConvertor implements JSON.Convertor + { + @Override + public void toJSON(Object obj, JSON.Output out) + { + } + + @Override + public Object fromJSON(Map map) + { + return new Custom(); + } + + public static class Custom + { + } + } + + @Test + public void testContext() + { + AsyncJSON.Factory factory = new AsyncJSON.Factory() + { + @Override + public AsyncJSON newAsyncJSON() + { + return new AsyncJSON(this) + { + @Override + protected Map newObject(Context context) + { + if (context.depth() == 1) + { + return new CustomMap(); + } + return super.newObject(context); + } + }; + } + }; + AsyncJSON parser = factory.newAsyncJSON(); + + String json = "[{" + + "\"channel\": \"/meta/handshake\"," + + "\"version\": \"1.0\"," + + "\"supportedConnectionTypes\": [\"long-polling\"]," + + "\"advice\": {\"timeout\": 0}" + + "}]"; + + assertTrue(parser.parse(UTF_8.encode(json))); + List messages = parser.complete(); + + for (CustomMap message : messages) + { + @SuppressWarnings("unchecked") + Map advice = (Map)message.get("advice"); + assertFalse(advice instanceof CustomMap); + } + } + + public static class CustomMap extends HashMap + { + } + + @Test + public void testCaching() + { + AsyncJSON.Factory factory = new AsyncJSON.Factory(); + String foo = "foo"; + factory.cache(foo); + AsyncJSON parser = factory.newAsyncJSON(); + + String json = "{\"foo\": [\"foo\", \"foo\"]}"; + parser.parse(UTF_8.encode(json)); + Map object = parser.complete(); + + Map.Entry entry = object.entrySet().iterator().next(); + assertSame(foo, entry.getKey()); + @SuppressWarnings("unchecked") + List array = (List)entry.getValue(); + for (String item : array) + { + assertSame(foo, item); + } + } + + @Test + public void testEncodedCaching() + { + AsyncJSON.Factory factory = new AsyncJSON.Factory(); + assertFalse(factory.cache("yèck")); + String foo = "foo\\yuck"; + assertTrue(factory.cache(foo)); + AsyncJSON parser = factory.newAsyncJSON(); + + String json = "{\"foo\\\\yuck\": [\"foo\\\\yuck\", \"foo\\\\yuck\"]}"; + parser.parse(UTF_8.encode(json)); + Map object = parser.complete(); + + Map.Entry entry = object.entrySet().iterator().next(); + assertSame(foo, entry.getKey()); + @SuppressWarnings("unchecked") + List array = (List)entry.getValue(); + for (String item : array) + { + assertSame(foo, item); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java index 0c7ecb2de0e..f7f14278671 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java @@ -614,7 +614,7 @@ public class ArrayTernaryTrie extends AbstractTrie boolean added = _trie.put(s, v); while (!added && _growby > 0) { - ArrayTernaryTrie bigger = new ArrayTernaryTrie<>(_trie._key.length + _growby); + ArrayTernaryTrie bigger = new ArrayTernaryTrie<>(_trie.isCaseInsensitive(), _trie._key.length + _growby); for (Map.Entry entry : _trie.entrySet()) { bigger.put(entry.getKey(), entry.getValue()); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java index 57f54f012c4..ae4f2c19ff5 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java @@ -111,8 +111,7 @@ public class AttributesMap implements Attributes, Dumpable if (attrs instanceof AttributesMap) return Collections.enumeration(((AttributesMap)attrs).keySet()); - List names = new ArrayList<>(); - names.addAll(Collections.list(attrs.getAttributeNames())); + List names = new ArrayList<>(Collections.list(attrs.getAttributeNames())); return Collections.enumeration(names); } 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 770a481b675..ef6b13efc66 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 @@ -19,11 +19,13 @@ package org.eclipse.jetty.util.thread; import java.io.Closeable; +import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.logging.StacklessLogging; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -36,6 +38,7 @@ import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.core.StringContains.containsString; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; public class QueuedThreadPoolTest extends AbstractThreadPoolTest @@ -753,6 +756,24 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest assertThrows(IllegalArgumentException.class, () -> new QueuedThreadPool(4, 8)); } + @Test + public void testJoinWithStopTimeout() throws Exception + { + final long stopTimeout = 100; + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setStopTimeout(100); + threadPool.start(); + + // Verify that join does not timeout after waiting twice the stopTimeout. + assertThrows(Throwable.class, () -> + assertTimeoutPreemptively(Duration.ofMillis(stopTimeout * 2), threadPool::join) + ); + + // After stopping the ThreadPool join should unblock. + LifeCycle.stop(threadPool); + assertTimeoutPreemptively(Duration.ofMillis(stopTimeout), threadPool::join); + } + @Test public void testDump() throws Exception { diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 44e4e411463..ca6942a810a 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -1200,7 +1200,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { ServletMapping mapping = new ServletMapping(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString())); mapping.setServletName(servletName); - mapping.setDefault(descriptor instanceof DefaultsDescriptor); + mapping.setFromDefaultDescriptor(descriptor instanceof DefaultsDescriptor); List paths = new ArrayList(); Iterator iter = node.iterator("url-pattern"); @@ -1221,9 +1221,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { //The same path has been mapped multiple times, either to a different servlet or the same servlet. //If its a different servlet, this is only valid to do if the old mapping was from a default descriptor. - if (p.equals(ps) && (sm.isDefault() || servletName.equals(sm.getServletName()))) + if (p.equals(ps) && (sm.isFromDefaultDescriptor() || servletName.equals(sm.getServletName()))) { - if (sm.isDefault()) + if (sm.isFromDefaultDescriptor()) { if (LOG.isDebugEnabled()) LOG.debug("{} in mapping {} from defaults descriptor is overridden by ", ps, sm, servletName); diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index 3d74b9c9e91..b30945be593 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -14,9 +14,11 @@ http://www.eclipse.org/jetty - websocket-core - websocket-util - websocket-util-server + + websocket-core-common + websocket-core-client + websocket-core-server + websocket-core-tests websocket-jetty-api websocket-jetty-common @@ -28,6 +30,9 @@ websocket-javax-client websocket-javax-server websocket-javax-tests + + websocket-util + websocket-util-server diff --git a/jetty-websocket/websocket-core-client/pom.xml b/jetty-websocket/websocket-core-client/pom.xml new file mode 100644 index 00000000000..5924b661527 --- /dev/null +++ b/jetty-websocket/websocket-core-client/pom.xml @@ -0,0 +1,82 @@ + + + + org.eclipse.jetty.websocket + websocket-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + websocket-core-client + Jetty :: Websocket :: Core :: Client + + + ${project.groupId}.core.client + + + + + org.eclipse.jetty.websocket + websocket-core-common + ${project.version} + + + org.eclipse.jetty + jetty-client + ${project.version} + + + org.eclipse.jetty + jetty-xml + ${project.version} + true + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + ban-ws-apis + + enforce + + + + + + org.eclipse.jetty.websocket:websocket-jetty-api + javax.websocket + + + + + + + ban-java-servlet-api + + enforce + + + + + + javax.servlet + servletapi + org.eclipse.jetty.orbit:javax.servlet + org.mortbay.jetty:servlet-api + jetty:servlet-api + jetty-servlet-api + + + + + + + + + + diff --git a/jetty-websocket/websocket-core/src/main/config/modules/websocket.mod b/jetty-websocket/websocket-core-client/src/main/config/modules/websocket.mod similarity index 100% rename from jetty-websocket/websocket-core/src/main/config/modules/websocket.mod rename to jetty-websocket/websocket-core-client/src/main/config/modules/websocket.mod diff --git a/jetty-websocket/websocket-core-client/src/main/java/module-info.java b/jetty-websocket/websocket-core-client/src/main/java/module-info.java new file mode 100644 index 00000000000..0e8dbf89111 --- /dev/null +++ b/jetty-websocket/websocket-core-client/src/main/java/module-info.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +module org.eclipse.jetty.websocket.core.client +{ + exports org.eclipse.jetty.websocket.core.client; + + requires org.slf4j; + requires transitive org.eclipse.jetty.client; + requires transitive org.eclipse.jetty.websocket.core.common; + + // Only required if using XmlHttpClientProvider. + requires static org.eclipse.jetty.xml; +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java similarity index 98% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java index fec8515b2f7..ab74b874ec1 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/ClientUpgradeRequest.java @@ -80,8 +80,8 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon protected final CompletableFuture futureCoreSession; private final WebSocketCoreClient wsClient; private FrameHandler frameHandler; - private Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer(); - private List upgradeListeners = new ArrayList<>(); + private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer(); + private final List upgradeListeners = new ArrayList<>(); private List requestedExtensions = new ArrayList<>(); public ClientUpgradeRequest(WebSocketCoreClient webSocketClient, URI requestURI) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpClientProvider.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpClientProvider.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpClientProvider.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpClientProvider.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/HttpUpgraderOverHTTP2.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeListener.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeListener.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeListener.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/UpgradeListener.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java similarity index 98% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java index e4aa450d181..7795e5460c2 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java +++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/WebSocketCoreClient.java @@ -40,7 +40,7 @@ public class WebSocketCoreClient extends ContainerLifeCycle private static final Logger LOG = LoggerFactory.getLogger(WebSocketCoreClient.class); private final HttpClient httpClient; - private WebSocketComponents components; + private final WebSocketComponents components; // TODO: Things to consider for inclusion in this class (or removal if they can be set elsewhere, like HttpClient) // - AsyncWrite Idle Timeout diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/XmlHttpClientProvider.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/XmlHttpClientProvider.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/client/XmlHttpClientProvider.java rename to jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/XmlHttpClientProvider.java diff --git a/jetty-websocket/websocket-core-common/pom.xml b/jetty-websocket/websocket-core-common/pom.xml new file mode 100644 index 00000000000..91cfb795f07 --- /dev/null +++ b/jetty-websocket/websocket-core-common/pom.xml @@ -0,0 +1,98 @@ + + + + org.eclipse.jetty.websocket + websocket-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + websocket-core-common + Jetty :: Websocket :: Core :: Common + + + ${project.groupId}.core.common + + + + + org.eclipse.jetty + jetty-io + ${project.version} + + + org.eclipse.jetty + jetty-http + ${project.version} + + + org.slf4j + slf4j-api + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + ban-ws-apis + + enforce + + + + + + org.eclipse.jetty.websocket:websocket-jetty-api + javax.websocket + + + + + + + ban-java-servlet-api + + enforce + + + + + + javax.servlet + servletapi + org.eclipse.jetty.orbit:javax.servlet + org.mortbay.jetty:servlet-api + jetty:servlet-api + jetty-servlet-api + + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + generate-manifest + + manifest + + + + *,org.eclipse.jetty.websocket.core.common.internal.* + + + + + + + + diff --git a/jetty-websocket/websocket-core-common/src/main/config/modules/websocket.mod b/jetty-websocket/websocket-core-common/src/main/config/modules/websocket.mod new file mode 100644 index 00000000000..da53af6d2e2 --- /dev/null +++ b/jetty-websocket/websocket-core-common/src/main/config/modules/websocket.mod @@ -0,0 +1,11 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Enable both jetty and javax websocket modules for deployed web applications. + +[tags] +websocket + +[depend] +websocket-jetty +websocket-javax diff --git a/jetty-websocket/websocket-core/src/main/java/module-info.java b/jetty-websocket/websocket-core-common/src/main/java/module-info.java similarity index 78% rename from jetty-websocket/websocket-core/src/main/java/module-info.java rename to jetty-websocket/websocket-core-common/src/main/java/module-info.java index c2fa64dda77..c2fb2c5276c 100644 --- a/jetty-websocket/websocket-core/src/main/java/module-info.java +++ b/jetty-websocket/websocket-core-common/src/main/java/module-info.java @@ -22,22 +22,16 @@ import org.eclipse.jetty.websocket.core.internal.IdentityExtension; import org.eclipse.jetty.websocket.core.internal.PerMessageDeflateExtension; import org.eclipse.jetty.websocket.core.internal.ValidationExtension; -module org.eclipse.jetty.websocket.core +module org.eclipse.jetty.websocket.core.common { exports org.eclipse.jetty.websocket.core; - exports org.eclipse.jetty.websocket.core.client; - exports org.eclipse.jetty.websocket.core.server; exports org.eclipse.jetty.websocket.core.exception; - exports org.eclipse.jetty.websocket.core.internal to org.eclipse.jetty.util; + exports org.eclipse.jetty.websocket.core.internal to org.eclipse.jetty.websocket.core.client, org.eclipse.jetty.websocket.core.server, org.eclipse.jetty.util; - requires jetty.servlet.api; - requires transitive org.eclipse.jetty.client; - requires transitive org.eclipse.jetty.server; + requires org.eclipse.jetty.http; + requires org.eclipse.jetty.io; requires org.slf4j; - // Only required if using XmlHttpClientProvider. - requires static org.eclipse.jetty.xml; - uses Extension; provides Extension with diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Behavior.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Behavior.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Behavior.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Behavior.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java similarity index 96% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java index cdbed64f7a6..e324d2c866e 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CloseStatus.java @@ -306,15 +306,12 @@ public class CloseStatus } // Reserved / not yet allocated - if ((statusCode == 1004) || // Reserved in RFC6455 (might be defined in the future) - ((statusCode >= 1016) && (statusCode <= 2999)) || // Reserved in RFC6455 (for future revisions, and extensions) - (statusCode >= 5000)) // RFC6455 Not allowed to be used for any purpose - { - return false; - } + // RFC6455 Not allowed to be used for any purpose + return (statusCode != 1004) && // Reserved in RFC6455 (might be defined in the future) + ((statusCode < 1016) || (statusCode > 2999)) && // Reserved in RFC6455 (for future revisions, and extensions) + (statusCode < 5000); // All others are allowed - return true; } public Frame toFrame() diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Configuration.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Configuration.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Configuration.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Configuration.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Extension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Extension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Extension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Extension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java similarity index 98% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java index 28be0a3e4f6..10a6fbb0eb6 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/ExtensionConfig.java @@ -38,7 +38,7 @@ import org.eclipse.jetty.util.Trie; */ public class ExtensionConfig { - private static Trie CACHE = new ArrayTrie<>(512); + private static final Trie CACHE = new ArrayTrie<>(512); static { @@ -315,7 +315,7 @@ public class ExtensionConfig { private final String parameterizedName; private String name; - private Map params = new HashMap<>(); + private final Map params = new HashMap<>(); public ParamParser(String parameterizedName) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Frame.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Frame.java similarity index 99% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Frame.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Frame.java index 5c44d9c4e77..9f7c1a74435 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/Frame.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/Frame.java @@ -193,11 +193,7 @@ public class Frame { return false; } - if (!Arrays.equals(mask, other.mask)) - { - return false; - } - return true; + return Arrays.equals(mask, other.mask); } public byte[] getMask() diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java similarity index 93% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java index 2e16bee68a2..265103b1324 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java @@ -19,8 +19,6 @@ package org.eclipse.jetty.websocket.core; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.core.server.Negotiation; /** * Interface for local WebSocket Endpoint Frame handling. @@ -31,11 +29,11 @@ import org.eclipse.jetty.websocket.core.server.Negotiation; * is instantiated by the application, either: *

*
    - *
  • On the server, the application layer must provide a {@link org.eclipse.jetty.websocket.core.server.WebSocketNegotiator} instance + *
  • On the server, the application layer must provide a {@code org.eclipse.jetty.websocket.core.server.WebSocketNegotiator} instance * to negotiate and accept websocket connections, which will return the FrameHandler instance to use from - * {@link org.eclipse.jetty.websocket.core.server.WebSocketNegotiator#negotiate(Negotiation)}.
  • - *
  • On the client, the application returns the FrameHandler instance to user from the {@link ClientUpgradeRequest} - * instance that it passes to the {@link org.eclipse.jetty.websocket.core.client.WebSocketCoreClient#connect(ClientUpgradeRequest)} method/
  • + * {@code org.eclipse.jetty.websocket.core.server.WebSocketNegotiator#negotiate(Negotiation)}. + *
  • On the client, the application returns the FrameHandler instance to user from the {@code ClientUpgradeRequest} + * instance that it passes to the {@code org.eclipse.jetty.websocket.core.client.WebSocketCoreClient#connect(ClientUpgradeRequest)} method/
  • *
*

* Once instantiated the FrameHandler follows is used as follows: diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/IncomingFrames.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/IncomingFrames.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/IncomingFrames.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/IncomingFrames.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/MessageHandler.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/MessageHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/MessageHandler.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/MessageHandler.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/OpCode.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OpCode.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/OpCode.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OpCode.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java similarity index 71% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java index 1f5d593f7fd..325132b097b 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketComponents.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.websocket.core; import java.util.zip.Deflater; -import javax.servlet.ServletContext; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; @@ -31,26 +30,14 @@ import org.eclipse.jetty.util.compression.InflaterPool; /** * A collection of components which are the resources needed for websockets such as * {@link ByteBufferPool}, {@link WebSocketExtensionRegistry}, and {@link DecoratedObjectFactory}. - * - * These components should be accessed through {@link WebSocketComponents#ensureWebSocketComponents} so that - * the instance can be shared by being stored as a bean on the ContextHandler. */ public class WebSocketComponents { - public static final String WEBSOCKET_COMPONENTS_ATTRIBUTE = WebSocketComponents.class.getName(); - - public static WebSocketComponents ensureWebSocketComponents(ServletContext servletContext) - { - // Ensure a mapping exists - WebSocketComponents components = (WebSocketComponents)servletContext.getAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE); - if (components == null) - { - components = new WebSocketComponents(); - servletContext.setAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE, components); - } - - return components; - } + private final DecoratedObjectFactory objectFactory; + private final WebSocketExtensionRegistry extensionRegistry; + private final ByteBufferPool bufferPool; + private final InflaterPool inflaterPool; + private final DeflaterPool deflaterPool; public WebSocketComponents() { @@ -69,12 +56,6 @@ public class WebSocketComponents this.inflaterPool = inflaterPool; } - private DecoratedObjectFactory objectFactory; - private WebSocketExtensionRegistry extensionRegistry; - private ByteBufferPool bufferPool; - private InflaterPool inflaterPool; - private DeflaterPool deflaterPool; - public ByteBufferPool getBufferPool() { return bufferPool; diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConstants.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConstants.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConstants.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConstants.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java similarity index 96% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java index a6b55884f68..09b6d51754a 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketExtensionRegistry.java @@ -30,7 +30,7 @@ import org.eclipse.jetty.util.TypeUtil; public class WebSocketExtensionRegistry implements Iterable> { - private Map> availableExtensions = new HashMap<>(); + private final Map> availableExtensions = new HashMap<>(); public WebSocketExtensionRegistry() { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/BadPayloadException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/BadPayloadException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/BadPayloadException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/BadPayloadException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java similarity index 97% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java index 6243d216de8..150700ec616 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/CloseException.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.websocket.core.exception; @SuppressWarnings("serial") public class CloseException extends WebSocketException { - private int statusCode; + private final int statusCode; public CloseException(int closeCode, String message) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/MessageTooLargeException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/MessageTooLargeException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/MessageTooLargeException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/MessageTooLargeException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/ProtocolException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/ProtocolException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/ProtocolException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/ProtocolException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/UpgradeException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/UpgradeException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/UpgradeException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/UpgradeException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketTimeoutException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketTimeoutException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketTimeoutException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketTimeoutException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketWriteTimeoutException.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketWriteTimeoutException.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketWriteTimeoutException.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/exception/WebSocketWriteTimeoutException.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java similarity index 99% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java index 28f3232c816..ccde09f3361 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java @@ -54,7 +54,7 @@ public class ExtensionStack implements IncomingFrames, OutgoingFrames, Dumpable private List extensions; private IncomingFrames incoming; private OutgoingFrames outgoing; - private Extension[] rsvClaims = new Extension[3]; + private final Extension[] rsvClaims = new Extension[3]; public ExtensionStack(WebSocketComponents components, Behavior behavior) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentExtension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentExtension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentingFlusher.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentingFlusher.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentingFlusher.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FragmentingFlusher.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java similarity index 97% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java index a4b7ccb73f4..9612f744651 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java @@ -51,8 +51,8 @@ public class FrameCaptureExtension extends AbstractExtension private Path incomingFramesPath; private Path outgoingFramesPath; - private AtomicInteger incomingCount = new AtomicInteger(0); - private AtomicInteger outgoingCount = new AtomicInteger(0); + private final AtomicInteger incomingCount = new AtomicInteger(0); + private final AtomicInteger outgoingCount = new AtomicInteger(0); private SeekableByteChannel incomingChannel; private SeekableByteChannel outgoingChannel; diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameEntry.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameEntry.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameEntry.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameEntry.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java similarity index 99% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java index c7633d291e5..e4483f6e719 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java @@ -511,7 +511,7 @@ public class FrameFlusher extends IteratingCallback private class Entry extends FrameEntry { private ByteBuffer headerBuffer; - private long timeOfCreation = System.currentTimeMillis(); + private final long timeOfCreation = System.currentTimeMillis(); private Entry(Frame frame, Callback callback, boolean batch) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameSequence.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameSequence.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameSequence.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameSequence.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Generator.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Generator.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Generator.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Generator.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/IdentityExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/IdentityExtension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/IdentityExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/IdentityExtension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Negotiated.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Negotiated.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Negotiated.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Negotiated.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/NullAppendable.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/NullAppendable.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/NullAppendable.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/NullAppendable.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Parser.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Parser.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/Parser.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/Parser.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/PerMessageDeflateExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/PerMessageDeflateExtension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/PerMessageDeflateExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/PerMessageDeflateExtension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/TransformingFlusher.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/TransformingFlusher.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/TransformingFlusher.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/TransformingFlusher.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ValidationExtension.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/ValidationExtension.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ValidationExtension.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/ValidationExtension.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java similarity index 99% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java index e83759e7285..0d61d603659 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketConnection.java @@ -63,8 +63,8 @@ public class WebSocketConnection extends AbstractConnection implements Connectio private long demand; private boolean fillingAndParsing; - private LongAdder messagesIn = new LongAdder(); - private LongAdder bytesIn = new LongAdder(); + private final LongAdder messagesIn = new LongAdder(); + private final LongAdder bytesIn = new LongAdder(); // Read / Parse variables private RetainableByteBuffer networkBuffer; diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCore.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCore.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCore.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCore.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java similarity index 97% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java index 53de09e2191..148d2fcf0ed 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketCoreSession.java @@ -29,7 +29,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Utf8Appendable; import org.eclipse.jetty.util.component.Dumpable; @@ -81,7 +80,6 @@ public class WebSocketCoreSession implements IncomingFrames, CoreSession, Dumpab private long maxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE; private Duration idleTimeout = WebSocketConstants.DEFAULT_IDLE_TIMEOUT; private Duration writeTimeout = WebSocketConstants.DEFAULT_WRITE_TIMEOUT; - private final ContextHandler contextHandler; public WebSocketCoreSession(FrameHandler handler, Behavior behavior, Negotiated negotiated, WebSocketComponents components) { @@ -90,26 +88,16 @@ public class WebSocketCoreSession implements IncomingFrames, CoreSession, Dumpab this.behavior = behavior; this.negotiated = negotiated; this.demanding = handler.isDemanding(); - - if (behavior == Behavior.SERVER) - { - ContextHandler.Context context = ContextHandler.getCurrentContext(); - this.contextHandler = (context != null) ? context.getContextHandler() : null; - } - else - { - this.contextHandler = null; - } - negotiated.getExtensions().initialize(new IncomingAdaptor(), new OutgoingAdaptor(), this); } - private void handle(Runnable runnable) + /** + * Can be overridden to scope into the correct classloader before calling application code. + * @param runnable the runnable to execute. + */ + protected void handle(Runnable runnable) { - if (contextHandler != null) - contextHandler.handle(runnable); - else - runnable.run(); + runnable.run(); } /** diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java rename to jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/WebSocketSessionState.java diff --git a/jetty-websocket/websocket-core/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.core.Extension b/jetty-websocket/websocket-core-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.core.Extension similarity index 100% rename from jetty-websocket/websocket-core/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.core.Extension rename to jetty-websocket/websocket-core-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.core.Extension diff --git a/jetty-websocket/websocket-core-server/pom.xml b/jetty-websocket/websocket-core-server/pom.xml new file mode 100644 index 00000000000..ab55a54ecb0 --- /dev/null +++ b/jetty-websocket/websocket-core-server/pom.xml @@ -0,0 +1,76 @@ + + + + org.eclipse.jetty.websocket + websocket-parent + 10.0.0-SNAPSHOT + + + 4.0.0 + websocket-core-server + Jetty :: Websocket :: Core :: Server + + + ${project.groupId}.core.server + + + + + org.eclipse.jetty.websocket + websocket-core-common + ${project.version} + + + org.eclipse.jetty + jetty-server + ${project.version} + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + ban-ws-apis + + enforce + + + + + + org.eclipse.jetty.websocket:websocket-jetty-api + javax.websocket + + + + + + + ban-java-servlet-api + + enforce + + + + + + javax.servlet + servletapi + org.eclipse.jetty.orbit:javax.servlet + org.mortbay.jetty:servlet-api + jetty:servlet-api + jetty-servlet-api + + + + + + + + + + diff --git a/jetty-websocket/websocket-core-server/src/main/config/modules/websocket.mod b/jetty-websocket/websocket-core-server/src/main/config/modules/websocket.mod new file mode 100644 index 00000000000..da53af6d2e2 --- /dev/null +++ b/jetty-websocket/websocket-core-server/src/main/config/modules/websocket.mod @@ -0,0 +1,11 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Enable both jetty and javax websocket modules for deployed web applications. + +[tags] +websocket + +[depend] +websocket-jetty +websocket-javax diff --git a/jetty-websocket/websocket-core-server/src/main/java/module-info.java b/jetty-websocket/websocket-core-server/src/main/java/module-info.java new file mode 100644 index 00000000000..fa7c47d2610 --- /dev/null +++ b/jetty-websocket/websocket-core-server/src/main/java/module-info.java @@ -0,0 +1,26 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +module org.eclipse.jetty.websocket.core.server +{ + exports org.eclipse.jetty.websocket.core.server; + + requires org.slf4j; + requires transitive org.eclipse.jetty.server; + requires transitive org.eclipse.jetty.websocket.core.common; +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/Handshaker.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/Handshaker.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/Handshaker.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/Handshaker.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/Negotiation.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/Negotiation.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/Negotiation.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/Negotiation.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketNegotiator.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketNegotiator.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketNegotiator.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketNegotiator.java diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java new file mode 100644 index 00000000000..2fefc439d09 --- /dev/null +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.core.server; + +import javax.servlet.ServletContext; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.DecoratedObjectFactory; +import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry; + +/** + * A collection of components which are the resources needed for websockets such as + * {@link ByteBufferPool}, {@link WebSocketExtensionRegistry}, and {@link DecoratedObjectFactory}. + * + * These components should be accessed through {@link WebSocketServerComponents#ensureWebSocketComponents} so that + * the instance can be shared by being stored as a bean on the ContextHandler. + */ +public class WebSocketServerComponents extends WebSocketComponents +{ + public static final String WEBSOCKET_COMPONENTS_ATTRIBUTE = WebSocketComponents.class.getName(); + + public static WebSocketComponents ensureWebSocketComponents(ServletContext servletContext) + { + // Ensure a mapping exists + WebSocketComponents components = (WebSocketComponents)servletContext.getAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE); + if (components == null) + { + components = new WebSocketServerComponents(); + servletContext.setAttribute(WEBSOCKET_COMPONENTS_ATTRIBUTE, components); + } + + return components; + } +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketUpgradeHandler.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketUpgradeHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketUpgradeHandler.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketUpgradeHandler.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java similarity index 93% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java index 1a0d84c1c5b..77259143103 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.websocket.core.Behavior; import org.eclipse.jetty.websocket.core.Configuration; @@ -132,7 +133,7 @@ public abstract class AbstractHandshaker implements Handshaker Negotiated negotiated = new Negotiated(baseRequest.getHttpURI().toURI(), protocol, baseRequest.isSecure(), extensionStack, WebSocketConstants.SPEC_VERSION_STRING); // Create the Session - WebSocketCoreSession coreSession = newWebSocketCoreSession(handler, negotiated, components); + WebSocketCoreSession coreSession = newWebSocketCoreSession(request, handler, negotiated, components); if (defaultCustomizer != null) defaultCustomizer.customize(coreSession); negotiator.customize(coreSession); @@ -195,9 +196,20 @@ public abstract class AbstractHandshaker implements Handshaker return true; } - protected WebSocketCoreSession newWebSocketCoreSession(FrameHandler handler, Negotiated negotiated, WebSocketComponents components) + protected WebSocketCoreSession newWebSocketCoreSession(HttpServletRequest request, FrameHandler handler, Negotiated negotiated, WebSocketComponents components) { - return new WebSocketCoreSession(handler, Behavior.SERVER, negotiated, components); + final ContextHandler contextHandler = ContextHandler.getContextHandler(request.getServletContext()); + return new WebSocketCoreSession(handler, Behavior.SERVER, negotiated, components) + { + @Override + protected void handle(Runnable runnable) + { + if (contextHandler != null) + contextHandler.handle(runnable); + else + runnable.run(); + } + }; } protected abstract WebSocketConnection createWebSocketConnection(Request baseRequest, WebSocketCoreSession coreSession); diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/HandshakerSelector.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/HandshakerSelector.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/HandshakerSelector.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/HandshakerSelector.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Handshaker.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Negotiation.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Negotiation.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Negotiation.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC6455Negotiation.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Handshaker.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Handshaker.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Handshaker.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Handshaker.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Negotiation.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Negotiation.java similarity index 100% rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Negotiation.java rename to jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/RFC8441Negotiation.java diff --git a/jetty-websocket/websocket-core/fuzzingclient.json b/jetty-websocket/websocket-core-tests/fuzzingclient.json similarity index 100% rename from jetty-websocket/websocket-core/fuzzingclient.json rename to jetty-websocket/websocket-core-tests/fuzzingclient.json diff --git a/jetty-websocket/websocket-core/fuzzingserver.json b/jetty-websocket/websocket-core-tests/fuzzingserver.json similarity index 100% rename from jetty-websocket/websocket-core/fuzzingserver.json rename to jetty-websocket/websocket-core-tests/fuzzingserver.json diff --git a/jetty-websocket/websocket-core/pom.xml b/jetty-websocket/websocket-core-tests/pom.xml similarity index 51% rename from jetty-websocket/websocket-core/pom.xml rename to jetty-websocket/websocket-core-tests/pom.xml index 2e976948002..f24b3607818 100644 --- a/jetty-websocket/websocket-core/pom.xml +++ b/jetty-websocket/websocket-core-tests/pom.xml @@ -7,105 +7,53 @@ 4.0.0 - websocket-core - Jetty :: Websocket :: Core + websocket-core-tests + Jetty :: Websocket :: Core :: Tests - ${project.groupId}.core + ${project.groupId}.core.tests - org.eclipse.jetty - jetty-util + org.eclipse.jetty.websocket + websocket-core-client ${project.version} - org.eclipse.jetty - jetty-io + org.eclipse.jetty.websocket + websocket-core-server ${project.version} - org.eclipse.jetty - jetty-http + org.eclipse.jetty.websocket + websocket-core-common ${project.version} - - org.slf4j - slf4j-api - - - org.eclipse.jetty - jetty-xml - ${project.version} - true - - - org.eclipse.jetty - jetty-client - ${project.version} - - - org.eclipse.jetty - jetty-server - ${project.version} - - org.eclipse.jetty jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - org.apache.maven.plugins - maven-enforcer-plugin - - - ban-ws-apis - - enforce - - - - - - org.eclipse.jetty.websocket:websocket-jetty-api - javax.websocket - - - - - - - ban-java-servlet-api - - enforce - - - - - - javax.servlet - servletapi - org.eclipse.jetty.orbit:javax.servlet - org.mortbay.jetty:servlet-api - jetty:servlet-api - jetty-servlet-api - - - - - - + maven-deploy-plugin + + + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + true + diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/AcceptHashTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AcceptHashTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/AcceptHashTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AcceptHashTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java similarity index 95% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java index 14a641f1cd7..da005f5ad69 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/CapturedHexPayloads.java @@ -26,7 +26,7 @@ import org.eclipse.jetty.util.Callback; public class CapturedHexPayloads implements OutgoingFrames { - private List captured = new ArrayList<>(); + private final List captured = new ArrayList<>(); @Override public void sendFrame(Frame frame, Callback callback, boolean batch) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/CloseStatusTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/CloseStatusTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/CloseStatusTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/CloseStatusTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java similarity index 97% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java index f787ae8fd89..e17524caaf9 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FlushTest.java @@ -41,9 +41,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class FlushTest { private WebSocketServer server; - private TestFrameHandler serverHandler = new TestFrameHandler(); + private final TestFrameHandler serverHandler = new TestFrameHandler(); private WebSocketCoreClient client; - private WebSocketComponents components = new WebSocketComponents(); + private final WebSocketComponents components = new WebSocketComponents(); @BeforeEach public void startup() throws Exception diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java similarity index 96% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java index a891d6d932f..9acce6884bd 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java @@ -40,9 +40,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class FrameBufferTest extends WebSocketTester { private WebSocketServer server; - private TestFrameHandler serverHandler = new TestFrameHandler(); + private final TestFrameHandler serverHandler = new TestFrameHandler(); private WebSocketCoreClient client; - private WebSocketComponents components = new WebSocketComponents(); + private final WebSocketComponents components = new WebSocketComponents(); @BeforeEach public void startup() throws Exception diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java similarity index 97% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java index af413ce7ea7..5d18c8027c9 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorFrameFlagsTest.java @@ -39,7 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; */ public class GeneratorFrameFlagsTest { - private static WebSocketComponents components = new WebSocketComponents(); + private static final WebSocketComponents components = new WebSocketComponents(); private WebSocketCoreSession coreSession; public static Stream data() diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java similarity index 97% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java index 449afdd7fd6..3e8b523c881 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorParserRoundTripTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class GeneratorParserRoundTripTest { - private ByteBufferPool bufferPool = new MappedByteBufferPool(); + private final ByteBufferPool bufferPool = new MappedByteBufferPool(); @Test public void testParserAndGenerator() throws Exception diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java index 5456640b22e..d03da52f3e4 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/GeneratorTest.java @@ -47,9 +47,9 @@ public class GeneratorTest { private static final Logger LOG = LoggerFactory.getLogger(Helper.class); - private static Generator generator = new Generator(); - private static WebSocketCoreSession coreSession = newWebSocketCoreSession(Behavior.SERVER); - private static WebSocketComponents components = new WebSocketComponents(); + private static final Generator generator = new Generator(); + private static final WebSocketCoreSession coreSession = newWebSocketCoreSession(Behavior.SERVER); + private static final WebSocketComponents components = new WebSocketComponents(); private static WebSocketCoreSession newWebSocketCoreSession(Behavior behavior) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/IncomingFramesCapture.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/IncomingFramesCapture.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/IncomingFramesCapture.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/IncomingFramesCapture.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java index e48d38d58a7..fc14e360b83 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java @@ -65,7 +65,7 @@ public class MessageHandlerTest coreSession = new CoreSession.Empty() { - private ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + private final ByteBufferPool byteBufferPool = new MappedByteBufferPool(); @Override public void sendFrame(Frame frame, Callback callback, boolean batch) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OpCodeTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OpCodeTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OpCodeTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OpCodeTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OutgoingFramesCapture.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OutgoingFramesCapture.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OutgoingFramesCapture.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OutgoingFramesCapture.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java index 1c067d2a157..33561dda1f1 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/OutgoingNetworkBytesCapture.java @@ -38,7 +38,7 @@ import static org.hamcrest.Matchers.lessThan; public class OutgoingNetworkBytesCapture implements OutgoingFrames { private final Generator generator; - private List captured; + private final List captured; public OutgoingNetworkBytesCapture(Generator generator) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParsePayloadLengthTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParsePayloadLengthTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParsePayloadLengthTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParsePayloadLengthTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java index 6fe3e60b6c5..4dfc798e772 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadCloseStatusCodesTest.java @@ -65,7 +65,7 @@ public class ParserBadCloseStatusCodesTest ); } - private ByteBufferPool bufferPool = new MappedByteBufferPool(); + private final ByteBufferPool bufferPool = new MappedByteBufferPool(); @ParameterizedTest(name = "closeCode={0} {1}") @MethodSource("data") diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java index a0dfcd2167a..f06c8960e24 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserBadOpCodesTest.java @@ -57,7 +57,7 @@ public class ParserBadOpCodesTest ); } - private ByteBufferPool bufferPool = new MappedByteBufferPool(); + private final ByteBufferPool bufferPool = new MappedByteBufferPool(); @ParameterizedTest(name = "opcode={0} {1}") @MethodSource("data") diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserCapture.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserCapture.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserCapture.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserCapture.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserGoodCloseStatusCodesTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserGoodCloseStatusCodesTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserGoodCloseStatusCodesTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserGoodCloseStatusCodesTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserReservedBitTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserReservedBitTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserReservedBitTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserReservedBitTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/ParserTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/ParserTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/RawFrameBuilder.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/RawFrameBuilder.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/RawFrameBuilder.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/RawFrameBuilder.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/SynchronousFrameHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/SynchronousFrameHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/SynchronousFrameHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/SynchronousFrameHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestUpgradeHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestUpgradeHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestUpgradeHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestUpgradeHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketNegotiator.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketNegotiator.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketNegotiator.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketNegotiator.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketUpgradeHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketUpgradeHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketUpgradeHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestWebSocketUpgradeHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/Timeouts.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/Timeouts.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/Timeouts.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/Timeouts.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java index bac1e2451d6..eeef7f56d4f 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java @@ -58,7 +58,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class WebSocketCloseTest extends WebSocketTester { - private static Logger LOG = LoggerFactory.getLogger(WebSocketCloseTest.class); + private static final Logger LOG = LoggerFactory.getLogger(WebSocketCloseTest.class); private static final String WS_SCHEME = "ws"; private static final String WSS_SCHEME = "wss"; diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java index 3ef42883dce..6c75606b6ad 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketFrameTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class WebSocketFrameTest { - private Generator generator = new Generator(); + private final Generator generator = new Generator(); private ByteBuffer generateWholeFrame(Generator generator, Frame frame) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java index ae25d7bbe94..e55ec678902 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java @@ -76,7 +76,7 @@ public class WebSocketNegotiationTest extends WebSocketTester private WebSocketServer server; private WebSocketCoreClient client; - private WebSocketComponents components = new WebSocketComponents(); + private final WebSocketComponents components = new WebSocketComponents(); @BeforeEach public void startup() throws Exception diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java index a96241d613d..1b564a76448 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java @@ -46,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class WebSocketOpenTest extends WebSocketTester { - private static Logger LOG = LoggerFactory.getLogger(WebSocketOpenTest.class); + private static final Logger LOG = LoggerFactory.getLogger(WebSocketOpenTest.class); private WebSocketServer server; private DemandingAsyncFrameHandler serverHandler; @@ -190,7 +190,7 @@ public class WebSocketOpenTest extends WebSocketTester static class DemandingAsyncFrameHandler extends TestAsyncFrameHandler { - private BiFunction onOpen; + private final BiFunction onOpen; DemandingAsyncFrameHandler(BiFunction onOpen) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketServer.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketServer.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketServer.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketServer.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java index 8ea7da37f89..4eb84f8cc0a 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketTester.java @@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.startsWith; public class WebSocketTester { - private static String NON_RANDOM_KEY = Base64.getEncoder().encodeToString("0123456701234567".getBytes()); + private static final String NON_RANDOM_KEY = Base64.getEncoder().encodeToString("0123456701234567".getBytes()); private static SslContextFactory.Client sslContextFactory; protected ByteBufferPool bufferPool; protected ByteBuffer buffer; diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnFrameHandler.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnFrameHandler.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnFrameHandler.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnFrameHandler.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java index 79252a0c1a3..3e297e52127 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java @@ -138,9 +138,9 @@ public class CoreAutobahnClient } private static final Logger LOG = LoggerFactory.getLogger(CoreAutobahnClient.class); - private URI baseWebsocketUri; - private WebSocketCoreClient client; - private String userAgent; + private final URI baseWebsocketUri; + private final WebSocketCoreClient client; + private final String userAgent; public CoreAutobahnClient(String hostname, int port, String userAgent) throws Exception { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java similarity index 95% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java index e5192c37dc3..3734c499324 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketClient.java @@ -36,11 +36,11 @@ import org.slf4j.LoggerFactory; public class ChatWebSocketClient { - private static Logger LOG = LoggerFactory.getLogger(ChatWebSocketClient.class); + private static final Logger LOG = LoggerFactory.getLogger(ChatWebSocketClient.class); - private URI baseWebsocketUri; - private WebSocketCoreClient client; - private MessageHandler handler; + private final URI baseWebsocketUri; + private final WebSocketCoreClient client; + private final MessageHandler handler; private String name = String.format("unknown@%x", ThreadLocalRandom.current().nextInt()); public ChatWebSocketClient(String hostname, int port) throws Exception diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java index 493946f2183..fee18160955 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/chat/ChatWebSocketServer.java @@ -45,7 +45,7 @@ import static org.eclipse.jetty.util.Callback.NOOP; public class ChatWebSocketServer { - private Set members = new HashSet<>(); + private final Set members = new HashSet<>(); private FrameHandler negotiate(Negotiation negotiation) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java index 8a4260cad67..3a4dc6741a4 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java @@ -45,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class WebSocketClientServerTest { - private static Logger LOG = LoggerFactory.getLogger(WebSocketClientServerTest.class); + private static final Logger LOG = LoggerFactory.getLogger(WebSocketClientServerTest.class); private WebSocketServer server; private TestFrameHandler serverHandler; diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AbstractExtensionTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/AbstractExtensionTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AbstractExtensionTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/AbstractExtensionTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionConfigTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionConfigTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionConfigTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionConfigTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionStackTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionStackTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionStackTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionStackTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java similarity index 97% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java index c8d080b0a13..1f5d2826da4 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java @@ -51,11 +51,11 @@ public class ExtensionTool { public class Tester { - private String requestedExtParams; - private ExtensionConfig extConfig; + private final String requestedExtParams; + private final ExtensionConfig extConfig; private Extension ext; - private Parser parser; - private IncomingFramesCapture capture; + private final Parser parser; + private final IncomingFramesCapture capture; private Tester(String parameterizedExtension) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/IdentityExtensionTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/IdentityExtensionTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/IdentityExtensionTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/IdentityExtensionTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java index 8d3a40bae68..5f28a82b544 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflaterBufferSizeTest.java @@ -53,8 +53,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class PerMessageDeflaterBufferSizeTest { private WebSocketServer server; - private TestFrameHandler serverHandler = new TestFrameHandler(); - private TestNegotiator testNegotiator = new TestNegotiator(); + private final TestFrameHandler serverHandler = new TestFrameHandler(); + private final TestNegotiator testNegotiator = new TestNegotiator(); private URI serverUri; private WebSocketCoreClient client; diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ValidationExtensionTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java index 2e376571e18..35f034d244c 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java @@ -57,7 +57,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class FrameFlusherTest { - private ByteBufferPool bufferPool = new MappedByteBufferPool(); + private final ByteBufferPool bufferPool = new MappedByteBufferPool(); private Scheduler scheduler; @BeforeEach diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndpoint.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java index 5d6944e0bdc..6af276077e1 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java @@ -51,8 +51,8 @@ class WebSocketProxy } private final Object lock = new Object(); - private WebSocketCoreClient client; - private URI serverUri; + private final WebSocketCoreClient client; + private final URI serverUri; public Client2Proxy client2Proxy = new Client2Proxy(); public Server2Proxy server2Proxy = new Server2Proxy(); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java similarity index 100% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java similarity index 99% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java rename to jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java index 66f235de234..d46db6e37f0 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java @@ -53,7 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class WebSocketServerTest extends WebSocketTester { - private static Logger LOG = LoggerFactory.getLogger(WebSocketServerTest.class); + private static final Logger LOG = LoggerFactory.getLogger(WebSocketServerTest.class); private WebSocketServer server; diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core-tests/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties rename to jetty-websocket/websocket-core-tests/src/test/resources/jetty-logging.properties diff --git a/jetty-websocket/websocket-core/src/test/resources/keystore.p12 b/jetty-websocket/websocket-core-tests/src/test/resources/keystore.p12 similarity index 100% rename from jetty-websocket/websocket-core/src/test/resources/keystore.p12 rename to jetty-websocket/websocket-core-tests/src/test/resources/keystore.p12 diff --git a/jetty-websocket/websocket-javax-client/pom.xml b/jetty-websocket/websocket-javax-client/pom.xml index b2fa1cd48aa..3d3f594e723 100644 --- a/jetty-websocket/websocket-javax-client/pom.xml +++ b/jetty-websocket/websocket-javax-client/pom.xml @@ -15,6 +15,15 @@ + + org.eclipse.jetty.toolchain + jetty-javax-websocket-api + + + org.eclipse.jetty.websocket + websocket-javax-common + ${project.version} + org.eclipse.jetty.websocket websocket-util @@ -22,7 +31,7 @@ org.eclipse.jetty.websocket - websocket-javax-common + websocket-core-client ${project.version} @@ -30,21 +39,12 @@ jetty-client ${project.version} - - org.eclipse.jetty.toolchain - jetty-javax-websocket-api - org.eclipse.jetty jetty-xml ${project.version} test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java index eb58cdef38e..362a4a64ac5 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java @@ -25,9 +25,10 @@ module org.eclipse.jetty.websocket.javax.client exports org.eclipse.jetty.websocket.javax.client; exports org.eclipse.jetty.websocket.javax.client.internal to org.eclipse.jetty.websocket.javax.server; - requires transitive jetty.websocket.api; - requires transitive org.eclipse.jetty.client; + requires org.eclipse.jetty.client; + requires org.eclipse.jetty.websocket.core.client; requires org.eclipse.jetty.websocket.javax.common; + requires transitive jetty.websocket.api; provides ContainerProvider with JavaxWebSocketClientContainerProvider; } diff --git a/jetty-websocket/websocket-javax-common/pom.xml b/jetty-websocket/websocket-javax-common/pom.xml index d606965d46c..5788cfeb68a 100644 --- a/jetty-websocket/websocket-javax-common/pom.xml +++ b/jetty-websocket/websocket-javax-common/pom.xml @@ -67,9 +67,8 @@ - org.eclipse.jetty.websocket - websocket-core - ${project.version} + org.eclipse.jetty.toolchain + jetty-javax-websocket-api org.eclipse.jetty.websocket @@ -77,8 +76,9 @@ ${project.version} - org.eclipse.jetty.toolchain - jetty-javax-websocket-api + org.eclipse.jetty.websocket + websocket-core-client + ${project.version} org.slf4j @@ -89,10 +89,5 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-javax-common/src/main/java/module-info.java b/jetty-websocket/websocket-javax-common/src/main/java/module-info.java index 08b85dda862..97397c3d70e 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/module-info.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/module-info.java @@ -23,10 +23,8 @@ module org.eclipse.jetty.websocket.javax.common exports org.eclipse.jetty.websocket.javax.common.encoders; exports org.eclipse.jetty.websocket.javax.common.messages; - requires transitive jetty.websocket.api; - requires transitive org.eclipse.jetty.http; - requires transitive org.eclipse.jetty.io; - requires transitive org.eclipse.jetty.websocket.core; - requires transitive org.eclipse.jetty.websocket.util; requires org.slf4j; + requires transitive jetty.websocket.api; + requires transitive org.eclipse.jetty.websocket.core.client; + requires transitive org.eclipse.jetty.websocket.util; } diff --git a/jetty-websocket/websocket-javax-server/pom.xml b/jetty-websocket/websocket-javax-server/pom.xml index d629de14878..e543c2dc12c 100644 --- a/jetty-websocket/websocket-javax-server/pom.xml +++ b/jetty-websocket/websocket-javax-server/pom.xml @@ -43,11 +43,6 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod b/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod index d46554bdfaf..bb9bd0b26c5 100644 --- a/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod +++ b/jetty-websocket/websocket-javax-server/src/main/config/modules/websocket-javax.mod @@ -11,7 +11,9 @@ client annotations [lib] -lib/websocket/websocket-core-${jetty.version}.jar +lib/websocket/websocket-core-common-${jetty.version}.jar +lib/websocket/websocket-core-client-${jetty.version}.jar +lib/websocket/websocket-core-server-${jetty.version}.jar lib/websocket/websocket-util-${jetty.version}.jar lib/websocket/websocket-util-server-${jetty.version}.jar lib/websocket/jetty-javax-websocket-api-1.1.2.jar diff --git a/jetty-websocket/websocket-javax-server/src/main/java/module-info.java b/jetty-websocket/websocket-javax-server/src/main/java/module-info.java index af5c88bbdd4..c4ad6598d80 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/module-info.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/module-info.java @@ -28,11 +28,11 @@ module org.eclipse.jetty.websocket.javax.server { exports org.eclipse.jetty.websocket.javax.server.config; - requires transitive org.eclipse.jetty.webapp; - requires transitive org.eclipse.jetty.websocket.javax.client; requires org.eclipse.jetty.websocket.javax.common; requires org.eclipse.jetty.websocket.util.server; requires org.slf4j; + requires transitive org.eclipse.jetty.webapp; + requires transitive org.eclipse.jetty.websocket.javax.client; provides ServletContainerInitializer with JavaxWebSocketServletContainerInitializer; provides ServerEndpointConfig.Configurator with ContainerDefaultConfigurator; diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java index 87739c6a434..af4cca34fe3 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer; import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; @@ -70,20 +71,14 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain String cp = context.getInitParameter(keyName); if (cp != null) { - if (TypeUtil.isTrue(cp)) - return true; - else - return false; + return TypeUtil.isTrue(cp); } // Next, try attribute on context Object enable = context.getAttribute(keyName); if (enable != null) { - if (TypeUtil.isTrue(enable)) - return true; - else - return false; + return TypeUtil.isTrue(enable); } return null; @@ -153,7 +148,7 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain JavaxWebSocketServerContainer serverContainer = JavaxWebSocketServerContainer.getContainer(context.getServletContext()); if (serverContainer == null) { - WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext()); + WebSocketComponents components = WebSocketServerComponents.ensureWebSocketComponents(context.getServletContext()); FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); serverContainer = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext()); diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java index 35a5ba4a86c..59f211b5f2e 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.exception.WebSocketException; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; @@ -95,7 +96,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer // Create the Jetty ServerContainer implementation container = new JavaxWebSocketServerContainer( WebSocketMapping.ensureMapping(servletContext, WebSocketMapping.DEFAULT_KEY), - WebSocketComponents.ensureWebSocketComponents(servletContext), + WebSocketServerComponents.ensureWebSocketComponents(servletContext), coreClientSupplier); contextHandler.addManaged(container); contextHandler.addEventListener(container); diff --git a/jetty-websocket/websocket-jetty-api/pom.xml b/jetty-websocket/websocket-jetty-api/pom.xml index 5f7a34e1f77..15edf958083 100644 --- a/jetty-websocket/websocket-jetty-api/pom.xml +++ b/jetty-websocket/websocket-jetty-api/pom.xml @@ -14,14 +14,6 @@ ${project.groupId}.api - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - diff --git a/jetty-websocket/websocket-jetty-client/pom.xml b/jetty-websocket/websocket-jetty-client/pom.xml index ad9d334d39f..cc3c39db832 100644 --- a/jetty-websocket/websocket-jetty-client/pom.xml +++ b/jetty-websocket/websocket-jetty-client/pom.xml @@ -25,6 +25,11 @@ websocket-jetty-common ${project.version} + + org.eclipse.jetty.websocket + websocket-core-client + ${project.version} + org.eclipse.jetty jetty-client @@ -39,11 +44,6 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/module-info.java b/jetty-websocket/websocket-jetty-client/src/main/java/module-info.java index aa113d80a62..27c6f8600c3 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/module-info.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/module-info.java @@ -20,9 +20,9 @@ module org.eclipse.jetty.websocket.jetty.client { exports org.eclipse.jetty.websocket.client; - requires transitive org.eclipse.jetty.client; - requires transitive org.eclipse.jetty.websocket.jetty.api; - requires org.eclipse.jetty.websocket.core; + requires org.eclipse.jetty.websocket.core.client; requires org.eclipse.jetty.websocket.jetty.common; requires org.slf4j; + requires transitive org.eclipse.jetty.client; + requires transitive org.eclipse.jetty.websocket.jetty.api; } diff --git a/jetty-websocket/websocket-jetty-common/pom.xml b/jetty-websocket/websocket-jetty-common/pom.xml index 83c726eb91d..b3809d06724 100644 --- a/jetty-websocket/websocket-jetty-common/pom.xml +++ b/jetty-websocket/websocket-jetty-common/pom.xml @@ -51,39 +51,10 @@ websocket-jetty-api ${project.version} - - org.eclipse.jetty.websocket - websocket-core - ${project.version} - org.eclipse.jetty.websocket websocket-util ${project.version} - - org.eclipse.jetty - jetty-util - ${project.version} - - - org.eclipse.jetty - jetty-io - ${project.version} - - - org.slf4j - slf4j-api - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/module-info.java b/jetty-websocket/websocket-jetty-common/src/main/java/module-info.java index 3b652531aab..4bac66d7684 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/module-info.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/module-info.java @@ -23,10 +23,11 @@ module org.eclipse.jetty.websocket.jetty.common { exports org.eclipse.jetty.websocket.common; - requires transitive org.eclipse.jetty.websocket.core; + requires org.eclipse.jetty.util; + requires org.slf4j; + requires transitive org.eclipse.jetty.websocket.core.common; requires transitive org.eclipse.jetty.websocket.jetty.api; requires transitive org.eclipse.jetty.websocket.util; - requires org.slf4j; provides ExtensionConfig.Parser with ExtensionConfigParser; } diff --git a/jetty-websocket/websocket-jetty-server/pom.xml b/jetty-websocket/websocket-jetty-server/pom.xml index ade9576c6e7..0387a72b773 100644 --- a/jetty-websocket/websocket-jetty-server/pom.xml +++ b/jetty-websocket/websocket-jetty-server/pom.xml @@ -15,27 +15,6 @@ - - org.eclipse.jetty - jetty-servlet - ${project.version} - - - org.eclipse.jetty - jetty-annotations - ${project.version} - - - org.eclipse.jetty - jetty-server - ${project.version} - - - org.eclipse.jetty - jetty-jmx - ${project.version} - true - org.eclipse.jetty.websocket websocket-jetty-api @@ -55,6 +34,22 @@ org.eclipse.jetty.toolchain jetty-servlet-api + + org.eclipse.jetty + jetty-servlet + ${project.version} + + + org.eclipse.jetty + jetty-annotations + ${project.version} + + + org.eclipse.jetty + jetty-jmx + ${project.version} + true + org.slf4j slf4j-api @@ -64,11 +59,6 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod b/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod index 9a93574f2bf..0dae0b934d1 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod +++ b/jetty-websocket/websocket-jetty-server/src/main/config/modules/websocket-jetty.mod @@ -11,7 +11,9 @@ client annotations [lib] -lib/websocket/websocket-core-${jetty.version}.jar +lib/websocket/websocket-core-common-${jetty.version}.jar +lib/websocket/websocket-core-client-${jetty.version}.jar +lib/websocket/websocket-core-server-${jetty.version}.jar lib/websocket/websocket-util-${jetty.version}.jar lib/websocket/websocket-util-server-${jetty.version}.jar lib/websocket/websocket-jetty-api-${jetty.version}.jar diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index 06a614efec2..bc6c85030e8 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -39,6 +39,7 @@ import org.eclipse.jetty.websocket.common.SessionTracker; import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.WebSocketException; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; @@ -73,7 +74,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements container = new JettyWebSocketServerContainer( contextHandler, WebSocketMapping.ensureMapping(servletContext, WebSocketMapping.DEFAULT_KEY), - WebSocketComponents.ensureWebSocketComponents(servletContext), executor); + WebSocketServerComponents.ensureWebSocketComponents(servletContext), executor); servletContext.setAttribute(JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE, container); contextHandler.addManaged(container); contextHandler.addEventListener(container); diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java index 448694577c5..be184470323 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java @@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; @@ -129,7 +130,7 @@ public abstract class JettyWebSocketServlet extends HttpServlet { ServletContext servletContext = getServletContext(); - components = WebSocketComponents.ensureWebSocketComponents(servletContext); + components = WebSocketServerComponents.ensureWebSocketComponents(servletContext); mapping = new WebSocketMapping(components); String max = getInitParameter("idleTimeout"); @@ -262,7 +263,7 @@ public abstract class JettyWebSocketServlet extends HttpServlet private static class WrappedJettyCreator implements WebSocketCreator { - private JettyWebSocketCreator creator; + private final JettyWebSocketCreator creator; private WrappedJettyCreator(JettyWebSocketCreator creator) { diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index 43b65eafd16..3b164c017f8 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; @@ -89,7 +90,7 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain */ private static JettyWebSocketServerContainer initialize(ServletContextHandler context) { - WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext()); + WebSocketComponents components = WebSocketServerComponents.ensureWebSocketComponents(context.getServletContext()); FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); JettyWebSocketServerContainer container = JettyWebSocketServerContainer.ensureContainer(context.getServletContext()); diff --git a/jetty-websocket/websocket-util-server/pom.xml b/jetty-websocket/websocket-util-server/pom.xml index c8b50273799..3fd5fc74a48 100644 --- a/jetty-websocket/websocket-util-server/pom.xml +++ b/jetty-websocket/websocket-util-server/pom.xml @@ -66,7 +66,7 @@ org.eclipse.jetty.websocket - websocket-core + websocket-core-server ${project.version} diff --git a/jetty-websocket/websocket-util-server/src/main/java/module-info.java b/jetty-websocket/websocket-util-server/src/main/java/module-info.java index ae57cc58894..c067bea8bf3 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/module-info.java +++ b/jetty-websocket/websocket-util-server/src/main/java/module-info.java @@ -23,5 +23,5 @@ module org.eclipse.jetty.websocket.util.server requires org.slf4j; requires transitive org.eclipse.jetty.servlet; - requires transitive org.eclipse.jetty.websocket.core; + requires transitive org.eclipse.jetty.websocket.core.server; } diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java index b0a9c030e90..5a64fc85ef0 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/WebSocketUpgradeFilter.java @@ -38,7 +38,7 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.websocket.core.Configuration; -import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -168,7 +168,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable if (mappingKey != null) mapping = WebSocketMapping.ensureMapping(context, mappingKey); else - mapping = new WebSocketMapping(WebSocketComponents.ensureWebSocketComponents(context)); + mapping = new WebSocketMapping(WebSocketServerComponents.ensureWebSocketComponents(context)); String max = config.getInitParameter("idleTimeout"); if (max == null) diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java index a96a55d315c..d76cbd53e9f 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java @@ -42,6 +42,7 @@ import org.eclipse.jetty.websocket.core.exception.WebSocketException; import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.server.Negotiation; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +66,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener if (mappingObject != null) { - if (WebSocketMapping.class.isInstance(mappingObject)) + if (mappingObject instanceof WebSocketMapping) return (WebSocketMapping)mappingObject; else throw new IllegalStateException( @@ -88,7 +89,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener if (mapping == null) { - mapping = new WebSocketMapping(WebSocketComponents.ensureWebSocketComponents(servletContext)); + mapping = new WebSocketMapping(WebSocketServerComponents.ensureWebSocketComponents(servletContext)); servletContext.setAttribute(mappingKey, mapping); } diff --git a/jetty-websocket/websocket-util/pom.xml b/jetty-websocket/websocket-util/pom.xml index 9412101ce9f..cafdccef949 100644 --- a/jetty-websocket/websocket-util/pom.xml +++ b/jetty-websocket/websocket-util/pom.xml @@ -48,7 +48,7 @@ org.eclipse.jetty.websocket - websocket-core + websocket-core-common ${project.version} @@ -60,10 +60,5 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-websocket/websocket-util/src/main/java/module-info.java b/jetty-websocket/websocket-util/src/main/java/module-info.java index 183e1fd2715..22dafa0caea 100644 --- a/jetty-websocket/websocket-util/src/main/java/module-info.java +++ b/jetty-websocket/websocket-util/src/main/java/module-info.java @@ -21,6 +21,8 @@ module org.eclipse.jetty.websocket.util exports org.eclipse.jetty.websocket.util; exports org.eclipse.jetty.websocket.util.messages; + requires org.eclipse.jetty.util; requires org.slf4j; - requires transitive org.eclipse.jetty.websocket.core; + requires org.eclipse.jetty.io; + requires transitive org.eclipse.jetty.websocket.core.common; } diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java index 8838a0797dd..6cd7e3fb95a 100644 --- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java @@ -36,6 +36,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import org.eclipse.jetty.logging.JettyLevel; import org.eclipse.jetty.logging.JettyLogger; import org.eclipse.jetty.logging.StdErrAppender; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -54,7 +55,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; import org.xml.sax.SAXException; import static java.nio.charset.StandardCharsets.UTF_8; @@ -1595,13 +1595,13 @@ public class XmlConfigurationTest JettyLogger jettyLogger = (JettyLogger)slf4jLogger; StdErrAppender appender = (StdErrAppender)jettyLogger.getAppender(); PrintStream oldStream = appender.getStream(); - int oldLevel = jettyLogger.getLevel(); + JettyLevel oldLevel = jettyLogger.getLevel(); try { // capture events appender.setStream(new PrintStream(logBytes, true)); // make sure we are seeing WARN level events - jettyLogger.setLevel(Level.WARN); + jettyLogger.setLevel(JettyLevel.WARN); action.run(); } diff --git a/pom.xml b/pom.xml index a2f2d8475d4..9a824ca94c3 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 9.0.29 9.4.8.Final 2.4.0 - 7.2 + 7.3.1 1.21 benchmarks 1.4.0 @@ -56,8 +56,7 @@ false - 5.3 - + 5.4 2.1.1.RELEASE @@ -66,6 +65,7 @@ ${project.build.directory}/local-repo src/it/settings.xml 0 + 1.14.2 @@ -1066,12 +1066,12 @@ org.testcontainers testcontainers - 1.14.1 + ${testcontainers.version} org.testcontainers junit-jupiter - 1.14.1 + ${testcontainers.version} @@ -1104,6 +1104,12 @@ slf4j-simple ${slf4j.version} + + org.slf4j + jul-to-slf4j + ${slf4j.version} + test + org.jboss.logging jboss-logging diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml index 22f7719be61..cca87657172 100644 --- a/tests/test-sessions/test-hazelcast-sessions/pom.xml +++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml @@ -109,7 +109,6 @@ org.slf4j jul-to-slf4j - ${slf4j.version} test diff --git a/tests/test-sessions/test-infinispan-sessions/pom.xml b/tests/test-sessions/test-infinispan-sessions/pom.xml index f30c57237aa..845f48b6e9e 100644 --- a/tests/test-sessions/test-infinispan-sessions/pom.xml +++ b/tests/test-sessions/test-infinispan-sessions/pom.xml @@ -11,7 +11,10 @@ http://www.eclipse.org/jetty ${project.groupId}.sessions.infinispan - 127.0.0.1 + + ${infinispan.version} + + jboss/infinispan-server @@ -141,19 +144,23 @@ org.slf4j jul-to-slf4j - ${slf4j.version} test - org.eclipse.jetty - jetty-slf4j-impl + org.slf4j + slf4j-simple + test + + + org.testcontainers + testcontainers test - remote + remote-session-tests hotrod.enabled @@ -166,34 +173,13 @@ org.apache.maven.plugins maven-surefire-plugin + + ${infinispan.docker.image.version} + ${infinispan.docker.image.name} + **/*.java - - **/ClusteredSessionScavengingTest.java - - - - - - - - alltests - - - alltests - true - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - FOOBAR - diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanSessionDataStoreTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanSessionDataStoreTest.java index faa76ba22bb..1eca7eca60a 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanSessionDataStoreTest.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanSessionDataStoreTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * InfinispanSessionDataStoreTest @@ -110,15 +110,7 @@ public class InfinispanSessionDataStoreTest extends AbstractSessionDataStoreTest ((InfinispanSessionDataStore)store).setCache(null); //test that loading it fails - try - { - store.load("222"); - fail("Session should be unreadable"); - } - catch (UnreadableSessionDataException e) - { - //expected exception - } + assertThrows(UnreadableSessionDataException.class, () -> store.load("222")); } /** diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SerializedInfinispanSessionDataStoreTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SerializedInfinispanSessionDataStoreTest.java index 5f45903196b..37f92e3b447 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SerializedInfinispanSessionDataStoreTest.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SerializedInfinispanSessionDataStoreTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * SerializedInfinispanSessionDataStoreTest @@ -107,15 +107,7 @@ public class SerializedInfinispanSessionDataStoreTest extends AbstractSessionDat ((InfinispanSessionDataStore)store).setCache(null); //test that loading it fails - try - { - store.load("222"); - fail("Session should be unreadable"); - } - catch (UnreadableSessionDataException e) - { - //expected exception - } + assertThrows(UnreadableSessionDataException.class,() -> store.load("222")); } /** diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanSessionDataStoreTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanSessionDataStoreTest.java index 91f0235d8a8..07d925881cc 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanSessionDataStoreTest.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanSessionDataStoreTest.java @@ -39,7 +39,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * RemoteInfinispanSessionDataStoreTest @@ -135,15 +135,7 @@ public class RemoteInfinispanSessionDataStoreTest extends AbstractSessionDataSto ((InfinispanSessionDataStore)store).setCache(null); //test that loading it fails - try - { - store.load("222"); - fail("Session should be unreadable"); - } - catch (UnreadableSessionDataException e) - { - //expected exception - } + assertThrows(UnreadableSessionDataException.class, () -> store.load("222")); } @Test diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanTestSupport.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanTestSupport.java index 1d3577df296..d0429185dba 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanTestSupport.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteInfinispanTestSupport.java @@ -31,31 +31,62 @@ import org.hibernate.search.cfg.Environment; import org.hibernate.search.cfg.SearchMapping; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ClientIntelligence; +import org.infinispan.client.hotrod.configuration.Configuration; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller; import org.infinispan.protostream.FileDescriptorSource; import org.infinispan.protostream.SerializationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.utility.MountableFile; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; /** * RemoteInfinispanTestSupport */ public class RemoteInfinispanTestSupport { + private static final Logger LOG = LoggerFactory.getLogger(RemoteInfinispanTestSupport.class); public static final String DEFAULT_CACHE_NAME = "session_test_cache"; public RemoteCache _cache; private String _name; public static RemoteCacheManager _manager; + private static final Logger INFINISPAN_LOG = + LoggerFactory.getLogger("org.eclipse.jetty.server.session.remote.infinispanLogs"); + + static GenericContainer infinispan; static { try { - String host = System.getProperty("hotrod.host", "127.0.0.1"); + //Testcontainers.exposeHostPorts(11222); + long start = System.currentTimeMillis(); + String infinispanVersion = System.getProperty("infinispan.docker.image.version", "9.4.8.Final"); + infinispan = + new GenericContainer(System.getProperty("infinispan.docker.image.name", "jboss/infinispan-server") + + ":" + infinispanVersion) + .withEnv("APP_USER","theuser") + .withEnv("APP_PASS","foobar") + .withEnv("MGMT_USER", "admin") + .withEnv("MGMT_PASS", "admin") + .waitingFor(new LogMessageWaitStrategy() + .withRegEx(".*Infinispan Server.*started in.*\\s")) + .withExposedPorts(4712,4713,8088,8089,8443,9990,9993,11211,11222,11223,11224) + .withLogConsumer(new Slf4jLogConsumer(INFINISPAN_LOG)); + infinispan.start(); + String host = infinispan.getContainerIpAddress(); + System.setProperty("hotrod.host", host); + int port = infinispan.getMappedPort(11222); + LOG.info("Infinispan container started for {}:{} - {}ms", host, port, + System.currentTimeMillis() - start); SearchMapping mapping = new SearchMapping(); mapping.entity(SessionData.class).indexed().providedId() .property("expiry", ElementType.METHOD).field(); @@ -63,10 +94,25 @@ public class RemoteInfinispanTestSupport Properties properties = new Properties(); properties.put(Environment.MODEL_MAPPING, mapping); - ConfigurationBuilder clientBuilder = new ConfigurationBuilder(); - clientBuilder.withProperties(properties).addServer().host(host).marshaller(new ProtoStreamMarshaller()); + ConfigurationBuilder configurationBuilder = new ConfigurationBuilder().withProperties(properties) + .addServer().host(host).port(port) + // we just want to limit connectivity to list of host:port we knows at start + // as infinispan create new host:port dynamically but due to how docker expose host/port we cannot do that + .clientIntelligence(ClientIntelligence.BASIC) + .marshaller(new ProtoStreamMarshaller()); - _manager = new RemoteCacheManager(clientBuilder.build()); + if (infinispanVersion.startsWith("1")) + { + configurationBuilder.security().authentication() + .realm("default") + .serverName("infinispan") + .saslMechanism("DIGEST-MD5") + .username("theuser").password("foobar"); + } + + Configuration configuration = configurationBuilder.build(); + + _manager = new RemoteCacheManager(configuration); FileDescriptorSource fds = new FileDescriptorSource(); fds.addProtoFiles("/session.proto"); @@ -86,11 +132,12 @@ public class RemoteInfinispanTestSupport } String content = baos.toString("UTF-8"); - _manager.getCache("___protobuf_metadata").put("session.proto", content); + _manager.administration().getOrCreateCache("___protobuf_metadata", (String)null).put("session.proto", content); } catch (Exception e) { - fail(e); + LOG.error(e.getMessage(), e); + throw new RuntimeException(e); } } @@ -114,7 +161,7 @@ public class RemoteInfinispanTestSupport public void setup() throws Exception { - _cache = _manager.getCache(_name); + _cache = _manager.administration().getOrCreateCache(_name,(String)null); } public void teardown() throws Exception diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/resources/simplelogger.properties b/tests/test-sessions/test-infinispan-sessions/src/test/resources/simplelogger.properties new file mode 100644 index 00000000000..cc6528beeee --- /dev/null +++ b/tests/test-sessions/test-infinispan-sessions/src/test/resources/simplelogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.remote.infinispanLogs=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.remote.RemoteInfinispanTestSupport=info diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index 8eb0cc52e99..c5d7c323231 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -8,9 +8,9 @@ test-jdbc-sessions Jetty Tests :: Sessions :: JDBC - http://www.eclipse.org/jetty ${project.groupId}.sessions.jdbc + 10.3.6 @@ -22,6 +22,15 @@ true + + org.apache.maven.plugins + maven-surefire-plugin + + + ${mariadb.docker.version} + + + @@ -55,13 +64,13 @@ test - org.apache.derby - derby + org.eclipse.jetty.toolchain + jetty-test-helper test - org.apache.derby - derbytools + org.testcontainers + testcontainers test @@ -74,5 +83,21 @@ jetty-test-helper test + + org.testcontainers + mariadb + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + test + + + org.mariadb.jdbc + mariadb-java-client + 2.6.0 + diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredInvalidationSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredInvalidationSessionTest.java index d81074084c1..18f430eb86d 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredInvalidationSessionTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredInvalidationSessionTest.java @@ -19,10 +19,12 @@ package org.eclipse.jetty.server.session; import org.junit.jupiter.api.AfterEach; +import org.testcontainers.junit.jupiter.Testcontainers; /** * ClusteredInvalidationSessionTest */ +@Testcontainers(disabledWithoutDocker = true) public class ClusteredInvalidationSessionTest extends AbstractClusteredInvalidationSessionTest { diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredOrphanedSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredOrphanedSessionTest.java index 7e0ee188959..fadcf04dd7c 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredOrphanedSessionTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredOrphanedSessionTest.java @@ -19,10 +19,12 @@ package org.eclipse.jetty.server.session; import org.junit.jupiter.api.AfterEach; +import org.testcontainers.junit.jupiter.Testcontainers; /** * ClusteredOrphanedSessionTest */ +@Testcontainers(disabledWithoutDocker = true) public class ClusteredOrphanedSessionTest extends AbstractClusteredOrphanedSessionTest { @Override diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java index 361fd148efa..571eca48998 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionMigrationTest.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpField; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -45,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; * Rather, it tests all of the machinery above the SessionDataStore. Thus, * to reduce test time, we only apply it to the JDBCSessionDataStore. */ +@Testcontainers(disabledWithoutDocker = true) public class ClusteredSessionMigrationTest extends AbstractTestBase { diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java index b3b8a0232e4..e57e27b0e6c 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClusteredSessionScavengingTest.java @@ -19,10 +19,12 @@ package org.eclipse.jetty.server.session; import org.junit.jupiter.api.AfterEach; +import org.testcontainers.junit.jupiter.Testcontainers; /** * ClusteredSessionScavengingTest */ +@Testcontainers(disabledWithoutDocker = true) public class ClusteredSessionScavengingTest extends AbstractClusteredSessionScavengingTest { @Override diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java index d41296f3a52..2beae163b9c 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JDBCSessionDataStoreTest.java @@ -20,10 +20,12 @@ package org.eclipse.jetty.server.session; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.junit.jupiter.Testcontainers; /** * JDBCSessionDataStoreTest */ +@Testcontainers(disabledWithoutDocker = true) public class JDBCSessionDataStoreTest extends AbstractSessionDataStoreTest { diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java index 2ff961416d1..2cb46544ae0 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestHelper.java @@ -33,6 +33,11 @@ import java.util.Set; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.ClassLoadingObjectInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -42,9 +47,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class JdbcTestHelper { - public static final String DRIVER_CLASS = "org.apache.derby.jdbc.EmbeddedDriver"; - public static final String DEFAULT_CONNECTION_URL = "jdbc:derby:memory:sessions;create=true"; - public static final String DEFAULT_SHUTDOWN_URL = "jdbc:derby:memory:sessions;drop=true"; + private static final Logger LOG = LoggerFactory.getLogger(JdbcTestHelper.class); + private static final Logger MARIADB_LOG = LoggerFactory.getLogger("org.eclipse.jetty.server.session.MariaDbLogs"); + + public static String DRIVER_CLASS; + public static String DEFAULT_CONNECTION_URL; + public static final int STALE_INTERVAL = 1; public static final String EXPIRY_COL = "extime"; @@ -60,28 +68,60 @@ public class JdbcTestHelper public static final String COOKIE_COL = "cooktime"; public static final String CREATE_COL = "ctime"; + static MariaDBContainer MARIAD_DB; + + static final String MARIA_DB_USER = "beer"; + static final String MARIA_DB_PASSWORD = "pacific_ale"; + static { - System.setProperty("derby.system.home", MavenTestingUtils.getTargetFile("test-derby").getAbsolutePath()); + try + { + long start = System.currentTimeMillis(); + MARIAD_DB = + new MariaDBContainer("mariadb:" + System.getProperty("mariadb.docker.version", "10.3.6")) + .withUsername(MARIA_DB_USER) + .withPassword(MARIA_DB_PASSWORD) + .withDatabaseName("sessions"); + MARIAD_DB.withLogConsumer(new Slf4jLogConsumer(MARIADB_LOG)).start(); + String containerIpAddress = MARIAD_DB.getContainerIpAddress(); + int mariadbPort = MARIAD_DB.getMappedPort(3306); + DEFAULT_CONNECTION_URL = MARIAD_DB.getJdbcUrl(); + DRIVER_CLASS = MARIAD_DB.getDriverClassName(); + LOG.info("Mariadb container started for {}:{} - {}ms", containerIpAddress, mariadbPort, + System.currentTimeMillis() - start); + DEFAULT_CONNECTION_URL = DEFAULT_CONNECTION_URL + "?user=" + MARIA_DB_USER + + "&password=" + MARIA_DB_PASSWORD; + LOG.info("DEFAULT_CONNECTION_URL: {}", DEFAULT_CONNECTION_URL); + } + catch (Exception e) + { + LOG.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } } public static void shutdown(String connectionUrl) throws Exception { - if (connectionUrl == null) - connectionUrl = DEFAULT_SHUTDOWN_URL; + try (Connection connection = getConnection()) + { + connection.prepareStatement("truncate table " + TABLE).executeUpdate(); + } + } - try - { - DriverManager.getConnection(connectionUrl); - } - catch (SQLException expected) - { - if (!"08006".equals(expected.getSQLState())) - { - throw expected; - } - } + public static DatabaseAdaptor buildDatabaseAdaptor() + { + DatabaseAdaptor da = new DatabaseAdaptor(); + da.setDriverInfo(DRIVER_CLASS, DEFAULT_CONNECTION_URL); + return da; + } + + public static Connection getConnection() + throws Exception + { + Class.forName(DRIVER_CLASS); + return DriverManager.getConnection(DEFAULT_CONNECTION_URL); } /** @@ -89,9 +129,7 @@ public class JdbcTestHelper */ public static SessionDataStoreFactory newSessionDataStoreFactory() { - DatabaseAdaptor da = new DatabaseAdaptor(); - da.setDriverInfo(DRIVER_CLASS, DEFAULT_CONNECTION_URL); - return newSessionDataStoreFactory(da); + return newSessionDataStoreFactory(buildDatabaseAdaptor()); } public static SessionDataStoreFactory newSessionDataStoreFactory(DatabaseAdaptor da) @@ -123,8 +161,7 @@ public class JdbcTestHelper public static void prepareTables() throws SQLException { - DatabaseAdaptor da = new DatabaseAdaptor(); - da.setDriverInfo(DRIVER_CLASS, DEFAULT_CONNECTION_URL); + DatabaseAdaptor da = buildDatabaseAdaptor(); JDBCSessionDataStore.SessionTableSchema sessionTableSchema = newSessionTableSchema(); sessionTableSchema.setDatabaseAdaptor(da); @@ -163,11 +200,8 @@ public class JdbcTestHelper public static boolean existsInSessionTable(String id, boolean verbose) throws Exception { - Class.forName(DRIVER_CLASS); - Connection con = null; - try + try (Connection con = getConnection()) { - con = DriverManager.getConnection(DEFAULT_CONNECTION_URL); PreparedStatement statement = con.prepareStatement("select * from " + TABLE + " where " + ID_COL + " = ?"); @@ -186,21 +220,15 @@ public class JdbcTestHelper else return result.next(); } - finally - { - if (con != null) - con.close(); - } } @SuppressWarnings("unchecked") public static boolean checkSessionPersisted(SessionData data) throws Exception { - Class.forName(DRIVER_CLASS); PreparedStatement statement = null; ResultSet result = null; - try (Connection con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);) + try (Connection con = getConnection()) { statement = con.prepareStatement( "select * from " + TABLE + @@ -265,9 +293,7 @@ public class JdbcTestHelper public static void insertSession(SessionData data) throws Exception { - - Class.forName(DRIVER_CLASS); - try (Connection con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);) + try (Connection con = getConnection()) { PreparedStatement statement = con.prepareStatement("insert into " + TABLE + " (" + ID_COL + ", " + CONTEXT_COL + ", virtualHost, " + LAST_NODE_COL + @@ -311,8 +337,7 @@ public class JdbcTestHelper long cookieSet, long lastSaved) throws Exception { - Class.forName(DRIVER_CLASS); - try (Connection con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);) + try (Connection con = getConnection()) { PreparedStatement statement = con.prepareStatement("insert into " + TABLE + " (" + ID_COL + ", " + CONTEXT_COL + ", virtualHost, " + LAST_NODE_COL + @@ -344,12 +369,9 @@ public class JdbcTestHelper public static Set getSessionIds() throws Exception { - HashSet ids = new HashSet(); - Class.forName(DRIVER_CLASS); - Connection con = null; - try + HashSet ids = new HashSet<>(); + try (Connection con = getConnection()) { - con = DriverManager.getConnection(DEFAULT_CONNECTION_URL); PreparedStatement statement = con.prepareStatement("select " + ID_COL + " from " + TABLE); ResultSet result = statement.executeQuery(); while (result.next()) @@ -358,10 +380,5 @@ public class JdbcTestHelper } return ids; } - finally - { - if (con != null) - con.close(); - } } } diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java index 17bcb26cb33..cc35bfad46c 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.webapp.WebAppContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -46,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * ReloadedSessionMissingClassTest */ @ExtendWith(WorkDirExtension.class) +@Testcontainers(disabledWithoutDocker = true) public class ReloadedSessionMissingClassTest { public WorkDir testdir; diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java index 40ef5a5f0fe..e9deadf92fc 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionTableSchemaTest.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.server.session; import java.io.ByteArrayInputStream; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -28,6 +27,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -39,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * Test the SessionTableSchema behaviour when the database treats "" as a NULL, * like Oracle does. */ +@Testcontainers(disabledWithoutDocker = true) public class SessionTableSchemaTest { DatabaseAdaptor _da; @@ -81,8 +82,7 @@ public class SessionTableSchemaTest public static void insertSessionWithoutAttributes(String id, String contextPath, String vhost) throws Exception { - Class.forName(JdbcTestHelper.DRIVER_CLASS); - try (Connection con = DriverManager.getConnection(JdbcTestHelper.DEFAULT_CONNECTION_URL);) + try (Connection con = JdbcTestHelper.getConnection()) { PreparedStatement statement = con.prepareStatement("insert into " + JdbcTestHelper.TABLE + " (" + JdbcTestHelper.ID_COL + ", " + JdbcTestHelper.CONTEXT_COL + ", virtualHost, " + JdbcTestHelper.LAST_NODE_COL + @@ -116,8 +116,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); //test if it can be seen try (Connection con = _da.getConnection()) @@ -127,7 +129,7 @@ public class SessionTableSchemaTest handler.setContextPath("/"); SessionContext sc = new SessionContext("0", handler.getServletContext()); //test the load statement - PreparedStatement s = _tableSchema.getLoadStatement(con, "1234", sc); + PreparedStatement s = _tableSchema.getLoadStatement(con, id, sc); ResultSet rs = s.executeQuery(); assertTrue(rs.next()); } @@ -141,8 +143,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); //test if it can be seen try (Connection con = _da.getConnection()) @@ -151,7 +155,7 @@ public class SessionTableSchemaTest handler.setContextPath("/"); SessionContext sc = new SessionContext("0", handler.getServletContext()); PreparedStatement s = _tableSchema.getCheckSessionExistsStatement(con, sc); - s.setString(1, "1234"); + s.setString(1, id); ResultSet rs = s.executeQuery(); assertTrue(rs.next()); } @@ -165,8 +169,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); //test if it can be deleted try (Connection con = _da.getConnection()) @@ -174,10 +180,10 @@ public class SessionTableSchemaTest ContextHandler handler = new ContextHandler(); handler.setContextPath("/"); SessionContext sc = new SessionContext("0", handler.getServletContext()); - PreparedStatement s = _tableSchema.getDeleteStatement(con, "1234", sc); + PreparedStatement s = _tableSchema.getDeleteStatement(con, id, sc); assertEquals(1, s.executeUpdate()); - assertFalse(JdbcTestHelper.existsInSessionTable("1234", false)); + assertFalse(JdbcTestHelper.existsInSessionTable(id, false)); } } @@ -189,8 +195,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { @@ -203,7 +211,7 @@ public class SessionTableSchemaTest (System.currentTimeMillis() + 100L)); ResultSet rs = s.executeQuery(); assertTrue(rs.next()); - assertEquals("1234", rs.getString(1)); + assertEquals(id, rs.getString(1)); } } @@ -215,8 +223,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { @@ -228,7 +238,7 @@ public class SessionTableSchemaTest (System.currentTimeMillis() + 100L)); ResultSet rs = s.executeQuery(); assertTrue(rs.next()); - assertEquals("1234", rs.getString(1)); + assertEquals(id, rs.getString(1)); } } @@ -240,8 +250,10 @@ public class SessionTableSchemaTest _da.initialize(); _tableSchema.prepareTables(); + String id = Long.toString(System.nanoTime()); + //insert a fake session at the root context - insertSessionWithoutAttributes("1234", "/", "0.0.0.0"); + insertSessionWithoutAttributes(id, "/", "0.0.0.0"); try (Connection con = _da.getConnection()) { @@ -249,7 +261,7 @@ public class SessionTableSchemaTest handler.setContextPath("/"); SessionContext sc = new SessionContext("0", handler.getServletContext()); PreparedStatement s = _tableSchema.getUpdateStatement(con, - "1234", + id, sc); s.setString(1, "0");//should be my node id diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java index c3a2b12c3d4..75d21faf105 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java @@ -21,10 +21,12 @@ package org.eclipse.jetty.server.session; import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; /** * WebAppObjectInSessionTest */ +@Testcontainers(disabledWithoutDocker = true) public class WebAppObjectInSessionTest extends AbstractWebAppObjectInSessionTest { diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/resources/simplelogger.properties b/tests/test-sessions/test-jdbc-sessions/src/test/resources/simplelogger.properties new file mode 100644 index 00000000000..6f00400db97 --- /dev/null +++ b/tests/test-sessions/test-jdbc-sessions/src/test/resources/simplelogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.MariaDbLogs=error +org.slf4j.simpleLogger.log.org.eclipse.jetty.server.session.JdbcTestHelper=info diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RemoveSessionTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RemoveSessionTest.java index c4cc8f31416..2cdb869a110 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RemoveSessionTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/RemoveSessionTest.java @@ -54,6 +54,7 @@ public class RemoveSessionTest String servletMapping = "/server"; DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); + cacheFactory.setFlushOnResponseCommit(true); cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); @@ -93,6 +94,9 @@ public class RemoveSessionTest assertEquals(0, ((DefaultSessionCache)m.getSessionCache()).getSessionsCurrent()); assertEquals(1, ((DefaultSessionCache)m.getSessionCache()).getSessionsMax()); assertEquals(1, ((DefaultSessionCache)m.getSessionCache()).getSessionsTotal()); + + //check the session is no longer in the cache + assertFalse(((AbstractSessionCache)m.getSessionCache()).contains(TestServer.extractSessionId(sessionCookie))); //check the session is not persisted any more assertFalse(m.getSessionCache().getSessionDataStore().exists(TestServer.extractSessionId(sessionCookie))); @@ -125,7 +129,8 @@ public class RemoveSessionTest String action = request.getParameter("action"); if ("create".equals(action)) { - request.getSession(true); + HttpSession s = request.getSession(true); + s.setAttribute("foo", "bar"); } else if ("delete".equals(action)) {