Support for JAX-RS 2.2 Java SE Bootstrapping API (#3839)
* Java SE Bootstrap API
Signed-off-by: Markus KARG <markus@headcrashing.eu>
diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
index 46c9b66..8ca8823 100644
--- a/containers/grizzly2-http/pom.xml
+++ b/containers/grizzly2-http/pom.xml
@@ -41,6 +41,10 @@
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java
new file mode 100644
index 0000000..581b7ee
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Grizzly {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServer implements Server {
+
+ private final GrizzlyHttpContainer container;
+
+ private final HttpServer httpServer;
+
+ GrizzlyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+ final String protocol = configuration.protocol();
+ final String host = configuration.host();
+ final int port = configuration.port();
+ final String rootPath = configuration.rootPath();
+ final SSLContext sslContext = configuration.sslContext();
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+ .sslClientAuthentication();
+ final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+ .orElse(TRUE);
+ final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+ .build();
+
+ this.container = new GrizzlyHttpContainer(application);
+ this.httpServer = GrizzlyHttpServerFactory.createHttpServer(uri, this.container, "HTTPS".equals(protocol),
+ new SSLEngineConfigurator(sslContext, false, sslClientAuthentication == OPTIONAL,
+ sslClientAuthentication == MANDATORY),
+ autoStart);
+ }
+
+ @Override
+ public final GrizzlyHttpContainer container() {
+ return this.container;
+ }
+
+ @Override
+ public final int port() {
+ return this.httpServer.getListener("grizzly").getPort();
+ }
+
+ @Override
+ public final CompletableFuture<Void> start() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ this.httpServer.start();
+ } catch (final IOException e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final CompletableFuture<Void> stop() {
+ return CompletableFuture.runAsync(this.httpServer::shutdownNow);
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(this.httpServer);
+ }
+
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java
new file mode 100644
index 0000000..82a99ee
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Grizzly {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServerProvider implements ServerProvider {
+
+ @Override
+ public final <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ return GrizzlyHttpServer.class == type || Server.class == type
+ ? type.cast(new GrizzlyHttpServer(application, configuration))
+ : null;
+ }
+}
diff --git a/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..dea5f90
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerProvider
\ No newline at end of file
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java
new file mode 100644
index 0000000..647d6d1
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link GrizzlyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServerProviderTest {
+
+ @Test(timeout = 15000)
+ public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new GrizzlyHttpServerProvider();
+ final Resource resource = new Resource();
+ final Application application = new Application() {
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(resource);
+ }
+ };
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return getPort();
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return FALSE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+ final Object nativeHandle = server.unwrap(Object.class);
+ final CompletionStage<?> start = server.start();
+ final Object startResult = start.toCompletableFuture().get();
+ final Container container = server.container();
+ final int port = server.port();
+ final String entity = ClientBuilder.newClient()
+ .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+ .get(String.class);
+ final CompletionStage<?> stop = server.stop();
+ final Object stopResult = stop.toCompletableFuture().get();
+
+ // then
+ assertThat(server, is(instanceOf(GrizzlyHttpServer.class)));
+ assertThat(nativeHandle, is(instanceOf(HttpServer.class)));
+ assertThat(startResult, is(nullValue()));
+ assertThat(container, is(instanceOf(GrizzlyHttpContainer.class)));
+ assertThat(port, is(greaterThan(0)));
+ assertThat(entity, is(resource.toString()));
+ assertThat(stopResult, is(nullValue()));
+ }
+
+ @Path("/")
+ protected static final class Resource {
+ @GET
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(GrizzlyHttpServerProviderTest.class.getName());
+
+ private static final int DEFAULT_PORT = 0;
+
+ private static final int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+ try {
+ final int i = Integer.parseInt(value);
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
+ }
+ return i;
+ } catch (final NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ @Test(timeout = 15000)
+ public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new GrizzlyHttpServerProvider();
+ final Application application = new Application();
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.FREE_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return TRUE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+ // then
+ assertThat(server.port(), is(greaterThan(0)));
+ }
+
+}
diff --git a/containers/jdk-http/pom.xml b/containers/jdk-http/pom.xml
index 3497cac..6601b6f 100644
--- a/containers/jdk-http/pom.xml
+++ b/containers/jdk-http/pom.xml
@@ -38,6 +38,10 @@
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java
new file mode 100644
index 0000000..16a445c
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Jersey {@code Server} implementation based on JDK {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServer implements Server {
+
+ private final JdkHttpHandlerContainer container;
+
+ private final HttpServer httpServer;
+
+ JdkHttpServer(final Application application, final JAXRS.Configuration configuration) {
+ final String protocol = configuration.protocol();
+ final String host = configuration.host();
+ final int port = configuration.port();
+ final String rootPath = configuration.rootPath();
+ final SSLContext sslContext = configuration.sslContext();
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+ .sslClientAuthentication();
+ final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+ .orElse(TRUE);
+ final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+ .build();
+
+ this.container = new JdkHttpHandlerContainer(application);
+ this.httpServer = JdkHttpServerFactory.createHttpServer(uri, this.container,
+ "HTTPS".equals(protocol) ? sslContext : null, sslClientAuthentication == OPTIONAL,
+ sslClientAuthentication == MANDATORY, autoStart);
+ }
+
+ @Override
+ public final JdkHttpHandlerContainer container() {
+ return this.container;
+ }
+
+ @Override
+ public final int port() {
+ return this.httpServer.getAddress().getPort();
+ }
+
+ @Override
+ public final CompletableFuture<Void> start() {
+ return CompletableFuture.runAsync(this.httpServer::start);
+ }
+
+ @Override
+ public final CompletableFuture<Void> stop() {
+ return CompletableFuture.runAsync(() -> this.httpServer.stop(0));
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(this.httpServer);
+ }
+
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
index 0f1fbb2..f91a0a8 100644
--- a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
@@ -26,6 +26,7 @@
import javax.ws.rs.ProcessingException;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
@@ -37,6 +38,7 @@
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
/**
@@ -177,8 +179,17 @@
}
private static HttpServer createHttpServer(final URI uri,
+ final JdkHttpHandlerContainer handler,
+ final SSLContext sslContext,
+ final boolean start) {
+ return createHttpServer(uri, handler, sslContext, false, false, start);
+ }
+
+ static HttpServer createHttpServer(final URI uri,
final JdkHttpHandlerContainer handler,
final SSLContext sslContext,
+ final boolean sslClientAuthWanted,
+ final boolean sslClientAuthNeeded,
final boolean start) {
if (uri == null) {
throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_NULL());
@@ -187,7 +198,14 @@
final String scheme = uri.getScheme();
final boolean isHttp = "http".equalsIgnoreCase(scheme);
final boolean isHttps = "https".equalsIgnoreCase(scheme);
- final HttpsConfigurator httpsConfigurator = sslContext != null ? new HttpsConfigurator(sslContext) : null;
+ final HttpsConfigurator httpsConfigurator = sslContext != null ? new HttpsConfigurator(sslContext) {
+ public final void configure(final HttpsParameters httpsParameters) {
+ final SSLParameters sslParameters = sslContext.getDefaultSSLParameters();
+ sslParameters.setWantClientAuth(sslClientAuthWanted);
+ sslParameters.setNeedClientAuth(sslClientAuthNeeded);
+ httpsParameters.setSSLParameters(sslParameters);
+ }
+ } : null;
if (isHttp) {
if (httpsConfigurator != null) {
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java
new file mode 100644
index 0000000..6c10cab
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Server provider for servers based on JDK {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServerProvider implements ServerProvider {
+
+ @Override
+ public final <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ return JdkHttpServer.class == type || Server.class == type
+ ? type.cast(new JdkHttpServer(application, configuration))
+ : null;
+ }
+}
diff --git a/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..2d8abc3
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jdkhttp.JdkHttpServerProvider
\ No newline at end of file
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
index 24998f9..b5c0be8 100644
--- a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
@@ -21,6 +21,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
import javax.ws.rs.core.UriBuilder;
import com.sun.net.httpserver.HttpServer;
@@ -37,7 +38,7 @@
public abstract class AbstractJdkHttpServerTester {
public static final String CONTEXT = "";
- private final int DEFAULT_PORT = 9998;
+ private final int DEFAULT_PORT = 0;
private static final Logger LOGGER = Logger.getLogger(AbstractJdkHttpServerTester.class.getName());
@@ -47,20 +48,24 @@
* @return The HTTP port of the URI
*/
protected final int getPort() {
+ if (server != null) {
+ return server.getAddress().getPort();
+ }
+
final String value =
AccessController.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
if (value != null) {
try {
final int i = Integer.parseInt(value);
- if (i <= 0) {
- throw new NumberFormatException("Value not positive.");
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
}
return i;
} catch (NumberFormatException e) {
LOGGER.log(Level.CONFIG,
"Value of 'jersey.config.test.container.port'"
- + " property is not a valid positive integer [" + value + "]."
+ + " property is not a valid non-negative integer [" + value + "]."
+ " Reverting to default [" + DEFAULT_PORT + "].",
e);
}
@@ -79,14 +84,22 @@
config.register(LoggingFeature.class);
final URI baseUri = getBaseUri();
server = JdkHttpServerFactory.createHttpServer(baseUri, config);
- LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + getBaseUri());
}
public void startServer(ResourceConfig config) {
final URI baseUri = getBaseUri();
config.register(LoggingFeature.class);
server = JdkHttpServerFactory.createHttpServer(baseUri, config);
- LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + getBaseUri());
+ }
+
+ public HttpServer startServer(final URI uri, final ResourceConfig config,
+ final SSLContext sslContext, final boolean start) {
+ config.register(LoggingFeature.class);
+ server = JdkHttpServerFactory.createHttpServer(uri, config, sslContext, start);
+ LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + UriBuilder.fromUri(uri).port(getPort()).build());
+ return server;
}
public URI getBaseUri() {
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java
new file mode 100644
index 0000000..c7c4e44
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Unit tests for {@link JdkHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServerProviderTest {
+
+ @Test(timeout = 15000)
+ public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new JdkHttpServerProvider();
+ final Resource resource = new Resource();
+ final Application application = new Application() {
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(resource);
+ }
+ };
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return getPort();
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return FALSE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+ final Object nativeHandle = server.unwrap(Object.class);
+ final CompletionStage<?> start = server.start();
+ final Object startResult = start.toCompletableFuture().get();
+ final Container container = server.container();
+ final int port = server.port();
+ final String entity = ClientBuilder.newClient()
+ .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+ .get(String.class);
+ final CompletionStage<?> stop = server.stop();
+ final Object stopResult = stop.toCompletableFuture().get();
+
+ // then
+ assertThat(server, is(instanceOf(JdkHttpServer.class)));
+ assertThat(nativeHandle, is(instanceOf(HttpServer.class)));
+ assertThat(startResult, is(nullValue()));
+ assertThat(container, is(instanceOf(JdkHttpHandlerContainer.class)));
+ assertThat(port, is(greaterThan(0)));
+ assertThat(entity, is(resource.toString()));
+ assertThat(stopResult, is(nullValue()));
+ }
+
+ @Path("/")
+ protected static final class Resource {
+ @GET
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(JdkHttpServerProviderTest.class.getName());
+
+ private static final int DEFAULT_PORT = 0;
+
+ private static final int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+ try {
+ final int i = Integer.parseInt(value);
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
+ }
+ return i;
+ } catch (final NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ @Test(timeout = 15000)
+ public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new JdkHttpServerProvider();
+ final Application application = new Application();
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.FREE_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return TRUE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+ // then
+ assertThat(server.port(), is(greaterThan(0)));
+ }
+
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
index 180f2bc..5fe94cb 100644
--- a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
@@ -64,9 +64,6 @@
private static final String TRUSTSTORE_SERVER_FILE = "./truststore_server";
private static final String TRUSTSTORE_SERVER_PWD = "asdfgh";
- private HttpServer server;
- private final URI httpsUri = UriBuilder.fromUri("https://localhost/").port(getPort()).build();
- private final URI httpUri = UriBuilder.fromUri("http://localhost/").port(getPort()).build();
private final ResourceConfig rc = new ResourceConfig(TestResource.class);
@Path("/testHttps")
@@ -83,7 +80,7 @@
*/
@Test
public void testCreateHttpsServerNoSslContext() throws Exception {
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+ HttpServer server = startServer(getHttpsUri(), rc, null, false);
assertThat(server, instanceOf(HttpsServer.class));
}
@@ -93,7 +90,7 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testStartHttpServerNoSslContext() throws Exception {
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, true);
+ startServer(getHttpsUri(), rc, null, true);
}
/**
@@ -103,13 +100,13 @@
*/
@Test(expected = SSLHandshakeException.class)
public void testCreateHttpsServerDefaultSslContext() throws Throwable {
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, SSLContext.getDefault(), true);
+ HttpServer server = startServer(getHttpsUri(), rc, SSLContext.getDefault(), true);
assertThat(server, instanceOf(HttpsServer.class));
// access the https server with not configured client
final Client client = ClientBuilder.newBuilder().newClient();
try {
- client.target(httpsUri).path("testHttps").request().get(String.class);
+ client.target(UriBuilder.fromUri("https://localhost/").port(getPort())).path("testHttps").request().get(String.class);
} catch (final ProcessingException e) {
throw e.getCause();
}
@@ -122,13 +119,13 @@
*/
@Test(expected = IOException.class)
public void testHttpsServerNoSslContextDelayedStart() throws Throwable {
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+ HttpServer server = startServer(getHttpsUri(), rc, null, false);
assertThat(server, instanceOf(HttpsServer.class));
server.start();
final Client client = ClientBuilder.newBuilder().newClient();
try {
- client.target(httpsUri).path("testHttps").request().get(String.class);
+ client.target(getHttpsUri()).path("testHttps").request().get(String.class);
} catch (final ProcessingException e) {
throw e.getCause();
}
@@ -140,7 +137,7 @@
*/
@Test(expected = IllegalStateException.class)
public void testConfigureSslContextAfterStart() throws Throwable {
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+ HttpServer server = startServer(getHttpsUri(), rc, null, false);
assertThat(server, instanceOf(HttpsServer.class));
server.start();
((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(getServerSslContext()));
@@ -154,14 +151,14 @@
public void testCreateHttpsServerRoundTrip() throws IOException {
final SSLContext serverSslContext = getServerSslContext();
- server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, serverSslContext, true);
+ HttpServer server = startServer(getHttpsUri(), rc, serverSslContext, true);
final SSLContext foundContext = ((HttpsServer) server).getHttpsConfigurator().getSSLContext();
assertEquals(serverSslContext, foundContext);
final SSLContext clientSslContext = getClientSslContext();
final Client client = ClientBuilder.newBuilder().sslContext(clientSslContext).build();
- final String response = client.target(httpsUri).path("testHttps").request().get(String.class);
+ final String response = client.target(UriBuilder.fromUri("https://localhost/").port(getPort())).path("testHttps").request().get(String.class);
assertEquals("test", response);
}
@@ -172,7 +169,7 @@
*/
@Test
public void testHttpWithSsl() throws IOException {
- server = JdkHttpServerFactory.createHttpServer(httpUri, rc, getServerSslContext(), true);
+ HttpServer server = startServer(getBaseUri(), rc, getServerSslContext(), true);
assertThat(server, instanceOf(HttpServer.class));
assertThat(server, not(instanceOf(HttpsServer.class)));
}
@@ -204,11 +201,7 @@
return sslConfigServer.createSSLContext();
}
- @After
- public void tearDown() {
- if (server != null) {
- server.stop(0);
- server = null;
- }
+ private URI getHttpsUri() {
+ return UriBuilder.fromUri("https://localhost/").port(getPort()).build();
}
}
diff --git a/containers/jetty-http/pom.xml b/containers/jetty-http/pom.xml
index d4c158e..0787c91 100644
--- a/containers/jetty-http/pom.xml
+++ b/containers/jetty-http/pom.xml
@@ -50,6 +50,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java
new file mode 100644
index 0000000..bb22343
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Jetty
+ * {@link org.eclipse.jetty.server.Server Server}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServer implements Server {
+
+ private final JettyHttpContainer container;
+
+ private final org.eclipse.jetty.server.Server httpServer;
+
+ JettyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+ final String protocol = configuration.protocol();
+ final String host = configuration.host();
+ final int port = configuration.port();
+ final String rootPath = configuration.rootPath();
+ final SSLContext sslContext = configuration.sslContext();
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+ .sslClientAuthentication();
+ final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+ .orElse(TRUE);
+ final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+ .build();
+
+ final SslContextFactory sslContextFactory;
+ if ("https".equals(uri.getScheme())) {
+ sslContextFactory = new SslContextFactory();
+ sslContextFactory.setSslContext(sslContext);
+ sslContextFactory.setWantClientAuth(sslClientAuthentication == OPTIONAL);
+ sslContextFactory.setNeedClientAuth(sslClientAuthentication == MANDATORY);
+ } else {
+ sslContextFactory = null;
+ }
+ this.container = ContainerFactory.createContainer(JettyHttpContainer.class, application);
+ this.httpServer = JettyHttpContainerFactory.createServer(uri, sslContextFactory, this.container, autoStart);
+ }
+
+ @Override
+ public final JettyHttpContainer container() {
+ return this.container;
+ }
+
+ @Override
+ public final int port() {
+ final ServerConnector serverConnector = (ServerConnector) this.httpServer.getConnectors()[0];
+ final int configuredPort = serverConnector.getPort();
+ final int localPort = serverConnector.getLocalPort();
+ return localPort < 0 ? configuredPort : localPort;
+ }
+
+ @Override
+ public final CompletableFuture<Void> start() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ this.httpServer.start();
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final CompletableFuture<Void> stop() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ this.httpServer.stop();
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(this.httpServer);
+ }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java
new file mode 100644
index 0000000..8dc3fe3
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Jetty
+ * {@link org.eclipse.jetty.server.Server Server}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServerProvider implements ServerProvider {
+
+ @Override
+ public final <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ return JettyHttpServer.class == type || Server.class == type
+ ? type.cast(new JettyHttpServer(application, configuration))
+ : null;
+ }
+}
diff --git a/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..403cc50
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.JettyHttpServerProvider
\ No newline at end of file
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
index dcfe4b7..d0bb608 100644
--- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
@@ -28,6 +28,7 @@
import org.glassfish.jersey.server.ResourceConfig;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
import org.junit.After;
/**
@@ -42,7 +43,7 @@
private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName());
public static final String CONTEXT = "";
- private static final int DEFAULT_PORT = 9998;
+ private static final int DEFAULT_PORT = 0;
/**
* Get the port to be used for test application deployments.
@@ -50,20 +51,24 @@
* @return The HTTP port of the URI
*/
protected final int getPort() {
+ if (server != null) {
+ return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+ }
+
final String value = AccessController
.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
if (value != null) {
try {
final int i = Integer.parseInt(value);
- if (i <= 0) {
- throw new NumberFormatException("Value not positive.");
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
}
return i;
} catch (NumberFormatException e) {
LOGGER.log(Level.CONFIG,
"Value of 'jersey.config.test.container.port'"
- + " property is not a valid positive integer [" + value + "]."
+ + " property is not a valid non-negative integer [" + value + "]."
+ " Reverting to default [" + DEFAULT_PORT + "].",
e);
}
@@ -82,13 +87,13 @@
config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
final URI baseUri = getBaseUri();
server = JettyHttpContainerFactory.createServer(baseUri, config);
- LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + getBaseUri());
}
public void startServer(ResourceConfig config) {
final URI baseUri = getBaseUri();
server = JettyHttpContainerFactory.createServer(baseUri, config);
- LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + getBaseUri());
}
public URI getBaseUri() {
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java
new file mode 100644
index 0000000..c4dc31e
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link JettyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServerProviderTest {
+
+ @Test(timeout = 15000)
+ public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new JettyHttpServerProvider();
+ final Resource resource = new Resource();
+ final Application application = new Application() {
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(resource);
+ }
+ };
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return getPort();
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return FALSE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+ final Object nativeHandle = server.unwrap(Object.class);
+ final CompletionStage<?> start = server.start();
+ final Object startResult = start.toCompletableFuture().get();
+ final Container container = server.container();
+ final int port = server.port();
+ final String entity = ClientBuilder.newClient()
+ .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+ .get(String.class);
+ final CompletionStage<?> stop = server.stop();
+ final Object stopResult = stop.toCompletableFuture().get();
+
+ // then
+ assertThat(server, is(instanceOf(JettyHttpServer.class)));
+ assertThat(nativeHandle, is(instanceOf(org.eclipse.jetty.server.Server.class)));
+ assertThat(startResult, is(nullValue()));
+ assertThat(container, is(instanceOf(JettyHttpContainer.class)));
+ assertThat(port, is(greaterThan(0)));
+ assertThat(entity, is(resource.toString()));
+ assertThat(stopResult, is(nullValue()));
+ }
+
+ @Path("/")
+ protected static final class Resource {
+ @GET
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(JettyHttpServerProviderTest.class.getName());
+
+ private static final int DEFAULT_PORT = 0;
+
+ private static final int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+ try {
+ final int i = Integer.parseInt(value);
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
+ }
+ return i;
+ } catch (final NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ @Test(timeout = 15000)
+ public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new JettyHttpServerProvider();
+ final Application application = new Application();
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.FREE_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return TRUE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+ // then
+ assertThat(server.port(), is(greaterThan(0)));
+ }
+
+}
diff --git a/containers/netty-http/pom.xml b/containers/netty-http/pom.xml
index a604811..e8deb20 100644
--- a/containers/netty-http/pom.xml
+++ b/containers/netty-http/pom.xml
@@ -42,6 +42,10 @@
<artifactId>jersey-netty-connector</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
index 124b36e..85d7b96 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
@@ -58,7 +58,7 @@
* Create and start Netty server.
*
* @param baseUri base uri.
- * @param configuration Jersey configuration.
+ * @param container Jersey container.
* @param sslContext Netty SSL context (can be null).
* @param block when {@code true}, this method will block until the server is stopped. When {@code false}, the
* execution will
@@ -66,25 +66,59 @@
* @return Netty channel instance.
* @throws ProcessingException when there is an issue with creating new container.
*/
- public static Channel createServer(final URI baseUri, final ResourceConfig configuration, SslContext sslContext,
+ public static Channel createServer(final URI baseUri, final NettyHttpContainer container, SslContext sslContext,
final boolean block)
throws ProcessingException {
+ final ServerBootstrap serverBootstrap = createServerBootstrap(baseUri, container, sslContext);
+ return startServer(getPort(baseUri), container, serverBootstrap, block);
+ }
+
+ /**
+ * Create but not start Netty server.
+ *
+ * @param baseUri base uri.
+ * @param container Jersey container.
+ * @param sslContext Netty SSL context (can be null).
+ * @return Netty bootstrap instance.
+ * @throws ProcessingException when there is an issue with creating new container.
+ */
+ static ServerBootstrap createServerBootstrap(final URI baseUri, final NettyHttpContainer container, SslContext sslContext) {
+
// Configure the server.
final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
- final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+ ServerBootstrap b = new ServerBootstrap();
+ b.option(ChannelOption.SO_BACKLOG, 1024);
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+
+ return b;
+ }
+
+ /**
+ * Start Netty server.
+ *
+ * @param port IP port to listen.
+ * @param container Jersey container.
+ * @param serverBootstrap Netty server bootstrap (i. e. prepared but unstarted server instance)
+ * @param block when {@code true}, this method will block until the server is stopped. When {@code false}, the
+ * execution will
+ * end immediately after the server is started.
+ * @return Netty channel instance.
+ * @throws ProcessingException when there is an issue with creating new container.
+ */
+ static Channel startServer(final int port, final NettyHttpContainer container, final ServerBootstrap serverBootstrap,
+ final boolean block)
+ throws ProcessingException {
try {
- ServerBootstrap b = new ServerBootstrap();
- b.option(ChannelOption.SO_BACKLOG, 1024);
- b.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+ final EventLoopGroup bossGroup = serverBootstrap.config().group();
+ final EventLoopGroup workerGroup = serverBootstrap.config().childGroup();
- int port = getPort(baseUri);
-
- Channel ch = b.bind(port).sync().channel();
+ Channel ch = serverBootstrap.bind(port).sync().channel();
ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
@@ -112,6 +146,24 @@
*
* @param baseUri base uri.
* @param configuration Jersey configuration.
+ * @param sslContext Netty SSL context (can be null).
+ * @param block when {@code true}, this method will block until the server is stopped. When {@code false}, the
+ * execution will
+ * end immediately after the server is started.
+ * @return Netty channel instance.
+ * @throws ProcessingException when there is an issue with creating new container.
+ */
+ public static Channel createServer(final URI baseUri, final ResourceConfig configuration, SslContext sslContext,
+ final boolean block)
+ throws ProcessingException {
+ return createServer(baseUri, new NettyHttpContainer(configuration), sslContext, block);
+ }
+
+ /**
+ * Create and start Netty server.
+ *
+ * @param baseUri base uri.
+ * @param configuration Jersey configuration.
* @param block when {@code true}, this method will block until the server is stopped. When {@code false}, the
* execution will
* end immediately after the server is started.
@@ -172,7 +224,7 @@
}
}
- private static int getPort(URI uri) {
+ static int getPort(URI uri) {
if (uri.getPort() == -1) {
if ("http".equalsIgnoreCase(uri.getScheme())) {
return 80;
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java
new file mode 100644
index 0000000..ee45da3
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import static java.lang.Boolean.TRUE;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.JdkSslContext;
+
+/**
+ * Jersey {@code Server} implementation based on Netty {@link Channel}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServer implements Server {
+
+ private final NettyHttpContainer container;
+
+ private final ServerBootstrap serverBootstrap;
+
+ private volatile Channel channel;
+
+ private final int port;
+
+ NettyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+ final String protocol = configuration.protocol();
+ final String host = configuration.host();
+ final int port = configuration.port();
+ final String rootPath = configuration.rootPath();
+ final SSLContext sslContext = configuration.sslContext();
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+ .sslClientAuthentication();
+ final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+ .orElse(TRUE);
+ final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+ .build();
+ this.port = NettyHttpContainerProvider.getPort(uri);
+
+ this.container = new NettyHttpContainer(application);
+ this.serverBootstrap = NettyHttpContainerProvider.createServerBootstrap(uri, this.container,
+ "HTTPS".equals(protocol)
+ ? new JdkSslContext(sslContext, false, nettyClientAuth(sslClientAuthentication))
+ : null);
+
+ if (autoStart) {
+ this.channel = NettyHttpContainerProvider.startServer(port, this.container, this.serverBootstrap, false);
+ }
+ }
+
+ private static final ClientAuth nettyClientAuth(
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication) {
+ switch (sslClientAuthentication) {
+ case MANDATORY:
+ return ClientAuth.REQUIRE;
+ case OPTIONAL:
+ return ClientAuth.OPTIONAL;
+ default:
+ return ClientAuth.NONE;
+ }
+ }
+
+ @Override
+ public final NettyHttpContainer container() {
+ return this.container;
+ }
+
+ @Override
+ public final int port() {
+ return this.channel == null ? this.port : ((InetSocketAddress) this.channel.localAddress()).getPort();
+ }
+
+ @Override
+ public final CompletableFuture<Object> start() {
+ return this.channel != null ? CompletableFuture.completedFuture(this.channel)
+ : CompletableFuture.supplyAsync(() -> {
+ try {
+ this.channel = NettyHttpContainerProvider.startServer(this.port, this.container,
+ this.serverBootstrap, false);
+ return this.channel;
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final CompletableFuture<Void> stop() {
+ return this.channel == null ? CompletableFuture.completedFuture(null) : CompletableFuture.supplyAsync(() -> {
+ try {
+ return this.channel.close().get();
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(this.channel == null ? this.serverBootstrap : this.channel);
+ }
+
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java
new file mode 100644
index 0000000..7238988
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+import io.netty.channel.Channel;
+
+/**
+ * Server provider for servers based on Netty {@link Channel}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServerProvider implements ServerProvider {
+
+ @Override
+ public final <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ return NettyHttpServer.class == type || Server.class == type
+ ? type.cast(new NettyHttpServer(application, configuration))
+ : null;
+ }
+}
diff --git a/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..61e6e1b
--- /dev/null
+++ b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.netty.httpserver.NettyHttpServerProvider
\ No newline at end of file
diff --git a/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java b/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java
new file mode 100644
index 0000000..951ad28
--- /dev/null
+++ b/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+
+/**
+ * Unit tests for {@link NettyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServerProviderTest {
+
+ @Test(timeout = 15000)
+ public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new NettyHttpServerProvider();
+ final Resource resource = new Resource();
+ final Application application = new Application() {
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(resource);
+ }
+ };
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return getPort();
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return FALSE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+ final Object nativeHandle = server.unwrap(Object.class);
+ final CompletionStage<?> start = server.start();
+ final Object startResult = start.toCompletableFuture().get();
+ final Container container = server.container();
+ final int port = server.port();
+ final String entity = ClientBuilder.newClient()
+ .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+ .get(String.class);
+ final CompletionStage<?> stop = server.stop();
+ final Object stopResult = stop.toCompletableFuture().get();
+
+ // then
+ assertThat(server, is(instanceOf(NettyHttpServer.class)));
+ assertThat(nativeHandle, is(instanceOf(ServerBootstrap.class)));
+ assertThat(startResult, is(instanceOf(Channel.class)));
+ assertThat(container, is(instanceOf(NettyHttpContainer.class)));
+ assertThat(port, is(greaterThan(0)));
+ assertThat(entity, is(resource.toString()));
+ assertThat(stopResult, is(nullValue()));
+ }
+
+ @Path("/")
+ protected static final class Resource {
+ @GET
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+ private static final Logger LOGGER = Logger.getLogger(NettyHttpServerProviderTest.class.getName());
+
+ private static final int DEFAULT_PORT = 0;
+
+ private static final int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+ try {
+ final int i = Integer.parseInt(value);
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
+ }
+ return i;
+ } catch (final NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ @Test(timeout = 15000)
+ public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new NettyHttpServerProvider();
+ final Application application = new Application();
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.FREE_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return TRUE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+ // then
+ assertThat(server.port(), is(greaterThan(0)));
+ }
+
+}
diff --git a/containers/simple-http/pom.xml b/containers/simple-http/pom.xml
index 7ef0351..b7c6f73 100644
--- a/containers/simple-http/pom.xml
+++ b/containers/simple-http/pom.xml
@@ -49,6 +49,10 @@
<groupId>org.simpleframework</groupId>
<artifactId>simple-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
index a9dc46d..db8dcdf 100644
--- a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
@@ -21,17 +21,19 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
-
-import javax.ws.rs.ProcessingException;
+import java.util.Optional;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.ProcessingException;
import org.glassfish.jersey.internal.util.collection.UnsafeValue;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.simple.internal.LocalizationMessages;
-
import org.simpleframework.http.core.Container;
import org.simpleframework.http.core.ContainerSocketProcessor;
+import org.simpleframework.transport.Socket;
import org.simpleframework.transport.SocketProcessor;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;
@@ -149,7 +151,55 @@
public SocketProcessor get() throws IOException {
return new ContainerSocketProcessor(container);
}
- });
+ }, true);
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes found by searching the classes referenced in the java classpath.
+ *
+ * @param address the URI to create the http server. The URI scheme must be equal to {@code https}
+ * . The URI user information and host are ignored. If the URI port is not present then
+ * port {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be used.
+ * The URI path, query and fragment components are ignored.
+ * @param context this is the SSL context used for SSL connections.
+ * @param config the resource configuration.
+ * @param sslClientAuthentication Secure socket client authentication policy.
+ * @param start whether the server shall listen to connections immediately
+ * @return the closeable connection, with the endpoint started.
+ * @throws ProcessingException thrown when problems during server creation.
+ * @throws IllegalArgumentException if {@code address} is {@code null}.
+ */
+ public static SimpleServer create(final URI address, final SSLContext context,
+ final SSLClientAuthentication sslClientAuthentication, final SimpleContainer container, final boolean start) {
+ return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+ @Override
+ public SocketProcessor get() throws IOException {
+ return new ContainerSocketProcessor(container) {
+ @Override
+ public final void process(final Socket socket) throws IOException {
+ final SSLEngine sslEngine = socket.getEngine();
+ if (sslEngine != null) {
+ switch (sslClientAuthentication) {
+ case MANDATORY: {
+ sslEngine.setNeedClientAuth(true);
+ break;
+ }
+ case OPTIONAL: {
+ sslEngine.setWantClientAuth(true);
+ break;
+ }
+ default: {
+ sslEngine.setNeedClientAuth(false);
+ break;
+ }
+ }
+ }
+ super.process(socket);
+ }
+ };
+ }
+ }, start);
}
/**
@@ -201,12 +251,13 @@
public SocketProcessor get() throws IOException {
return new ContainerSocketProcessor(container, count, select);
}
- });
+ }, true);
}
private static SimpleServer _create(final URI address, final SSLContext context,
final SimpleContainer container,
- final UnsafeValue<SocketProcessor, IOException> serverProvider)
+ final UnsafeValue<SocketProcessor, IOException> serverProvider,
+ final boolean start)
throws ProcessingException {
if (address == null) {
throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
@@ -236,22 +287,26 @@
final SocketProcessor server = serverProvider.get();
connection = new SocketConnection(server, analyzer);
+ final SimpleServer simpleServer = new SimpleServer() {
+ private InetSocketAddress socketAddr = listen;
- final SocketAddress socketAddr = connection.connect(listen, context);
- container.onServerStart();
-
- return new SimpleServer() {
+ @Override
+ public final void start() throws IOException {
+ this.socketAddr = (InetSocketAddress) connection.connect(listen, context);
+ container.onServerStart();
+ }
@Override
public void close() throws IOException {
container.onServerStop();
analyzer.stop();
connection.close();
+ this.socketAddr = listen;
}
@Override
public int getPort() {
- return ((InetSocketAddress) socketAddr).getPort();
+ return this.socketAddr.getPort();
}
@Override
@@ -268,6 +323,12 @@
}
}
};
+
+ if (start) {
+ simpleServer.start();
+ }
+
+ return simpleServer;
} catch (final IOException ex) {
throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), ex);
}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java
new file mode 100644
index 0000000..4176fbb
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import static java.lang.Boolean.TRUE;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Simple framework
+ * {@link SimpleServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServer implements Server {
+
+ private final SimpleContainer container;
+
+ private final SimpleServer simpleServer;
+
+ SimpleHttpServer(final Application application, final JAXRS.Configuration configuration) {
+ final String protocol = configuration.protocol();
+ final String host = configuration.host();
+ final int port = configuration.port();
+ final String rootPath = configuration.rootPath();
+ final SSLContext sslContext = configuration.sslContext();
+ final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+ .sslClientAuthentication();
+ final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+ .orElse(TRUE);
+ final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+ .build();
+
+ this.container = new SimpleContainer(application);
+ this.simpleServer = SimpleContainerFactory.create(uri, "HTTPS".equals(protocol) ? sslContext : null,
+ sslClientAuthentication, this.container, autoStart);
+ }
+
+ @Override
+ public final SimpleContainer container() {
+ return this.container;
+ }
+
+ @Override
+ public final int port() {
+ return this.simpleServer.getPort();
+ }
+
+ @Override
+ public final CompletableFuture<Void> start() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ this.simpleServer.start();
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final CompletableFuture<Void> stop() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ this.simpleServer.close();
+ } catch (final Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(this.simpleServer);
+ }
+
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java
new file mode 100644
index 0000000..70b40b9
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Simple framework {@link SimpleServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServerProvider implements ServerProvider {
+
+ @Override
+ public final <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ return SimpleHttpServer.class == type || Server.class == type
+ ? type.cast(new SimpleHttpServer(application, configuration))
+ : null;
+ }
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
index 02aec75..b644f7f 100644
--- a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
@@ -17,6 +17,7 @@
package org.glassfish.jersey.simple;
import java.io.Closeable;
+import java.io.IOException;
/**
* Simple server facade providing convenient methods to obtain info about the server (i.e. port).
@@ -26,6 +27,8 @@
*/
public interface SimpleServer extends Closeable {
+ public void start() throws IOException;
+
/**
* The port the server is listening to for incomming HTTP connections. If the port is not
* specified the {@link org.glassfish.jersey.server.spi.Container.DEFAULT_PORT} is used.
diff --git a/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..54d8c53
--- /dev/null
+++ b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.simple.SimpleHttpServerProvider
\ No newline at end of file
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
index eb653ca..9ce8094 100644
--- a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
@@ -39,7 +39,7 @@
public abstract class AbstractSimpleServerTester {
public static final String CONTEXT = "";
- private final int DEFAULT_PORT = 9998;
+ private final int DEFAULT_PORT = 0;
private static final Logger LOGGER = Logger.getLogger(AbstractSimpleServerTester.class.getName());
@@ -49,21 +49,25 @@
* @return The HTTP port of the URI
*/
protected final int getPort() {
+ if (server != null) {
+ return server.getPort();
+ }
+
final String value = AccessController
.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
if (value != null) {
try {
final int i = Integer.parseInt(value);
- if (i <= 0) {
- throw new NumberFormatException("Value not positive.");
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
}
return i;
} catch (NumberFormatException e) {
LOGGER.log(
Level.CONFIG,
"Value of 'jersey.config.test.container.port'"
- + " property is not a valid positive integer [" + value + "]."
+ + " property is not a valid non-negative integer [" + value + "]."
+ " Reverting to default [" + DEFAULT_PORT + "].",
e);
}
@@ -83,28 +87,28 @@
config.register(LoggingFeature.class);
final URI baseUri = getBaseUri();
server = SimpleContainerFactory.create(baseUri, config);
- LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
}
public void startServerNoLoggingFilter(Class... resources) {
ResourceConfig config = new ResourceConfig(resources);
final URI baseUri = getBaseUri();
server = SimpleContainerFactory.create(baseUri, config);
- LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
}
public void startServer(ResourceConfig config) {
final URI baseUri = getBaseUri();
config.register(LoggingFeature.class);
server = SimpleContainerFactory.create(baseUri, config);
- LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
}
public void startServer(ResourceConfig config, int count, int select) {
final URI baseUri = getBaseUri();
config.register(LoggingFeature.class);
server = SimpleContainerFactory.create(baseUri, config, count, select);
- LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+ LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
}
public URI getBaseUri() {
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java
new file mode 100644
index 0000000..fe483a0
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link SimpleHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServerProviderTest {
+
+ @Test(timeout = 15000)
+ public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new SimpleHttpServerProvider();
+ final Resource resource = new Resource();
+ final Application application = new Application() {
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(resource);
+ }
+ };
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return getPort();
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return FALSE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+ final Object nativeHandle = server.unwrap(Object.class);
+ final CompletionStage<?> start = server.start();
+ final Object startResult = start.toCompletableFuture().get();
+ final Container container = server.container();
+ final int port = server.port();
+ final String entity = ClientBuilder.newClient()
+ .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+ .get(String.class);
+ final CompletionStage<?> stop = server.stop();
+ final Object stopResult = stop.toCompletableFuture().get();
+
+ // then
+ assertThat(server, is(instanceOf(SimpleHttpServer.class)));
+ assertThat(nativeHandle, is(instanceOf(SimpleServer.class)));
+ assertThat(startResult, is(nullValue()));
+ assertThat(container, is(instanceOf(SimpleContainer.class)));
+ assertThat(port, is(greaterThan(0)));
+ assertThat(entity, is(resource.toString()));
+ assertThat(stopResult, is(nullValue()));
+ }
+
+ @Path("/")
+ protected static final class Resource {
+ @GET
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(SimpleHttpServerProviderTest.class.getName());
+
+ private static final int DEFAULT_PORT = 0;
+
+ private static final int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+ try {
+ final int i = Integer.parseInt(value);
+ if (i < 0) {
+ throw new NumberFormatException("Value is negative.");
+ }
+ return i;
+ } catch (final NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ @Test(timeout = 15000)
+ public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+ // given
+ final ServerProvider serverProvider = new SimpleHttpServerProvider();
+ final Application application = new Application();
+ final JAXRS.Configuration configuration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTP";
+ case JAXRS.Configuration.HOST:
+ return "localhost";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.FREE_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "/";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return SSLClientAuthentication.NONE;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ try {
+ return SSLContext.getDefault();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ case ServerProperties.AUTO_START:
+ return TRUE;
+ default:
+ return null;
+ }
+ };
+
+ // when
+ final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+ // then
+ assertThat(server.port(), is(greaterThan(0)));
+ }
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java
new file mode 100644
index 0000000..9bd4656
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Factory for creating specific HTTP servers.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class ServerFactory {
+
+ /**
+ * Prevents instantiation.
+ */
+ private ServerFactory() {
+ }
+
+ /**
+ * Creates a server of a given type which runs the given application using the
+ * given bootstrap configuration.
+ * <p>
+ * The list of service-providers supporting the {@link ServerProvider}
+ * service-provider will be iterated over until one returns a non-null server
+ * instance.
+ * <p>
+ *
+ * @param <T>
+ * the type of the server.
+ * @param type
+ * the type of the server. Providers SHOULD support at least
+ * {@link Server}.
+ * @param application
+ * The application to host.
+ * @param configuration
+ * The configuration (host, port, etc.) to be used for bootstrapping.
+ * @return the created server.
+ * @throws ProcessingException
+ * if there is an error creating the server.
+ * @throws IllegalArgumentException
+ * if no server provider supports the type.
+ */
+ public static <T extends Server> T createServer(final Class<T> type, final Application application,
+ final JAXRS.Configuration configuration) {
+ for (final ServerProvider serverProvider : ServiceFinder.find(ServerProvider.class)) {
+ final T server = serverProvider.createServer(type, application, configuration);
+ if (server != null) {
+ return server;
+ }
+ }
+
+ throw new IllegalArgumentException("No server provider supports the type " + type);
+ }
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
index fafb73a..3696193 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
@@ -24,6 +24,7 @@
import org.glassfish.jersey.internal.util.PropertiesClass;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.PropertyAlias;
+import org.glassfish.jersey.server.spi.Server;
/**
@@ -38,6 +39,23 @@
public final class ServerProperties {
/**
+ * Defines the implementation of {@link Server} to bootstrap.
+ * <p>
+ * By default auto-selects the first server provider found.
+ * </p>
+ */
+ public static final String HTTP_SERVER_CLASS = "jersey.config.server.httpServerClass";
+
+ /**
+ * Whether to automatically startup {@link Server} at bootstrap.
+ * <p>
+ * By default, servers are immediately listening to connections after bootstrap,
+ * so no explicit invocation of {@link Server#start()} is needed.
+ * </p>
+ */
+ public static final String AUTO_START = "jersey.config.server.autoStart";
+
+ /**
* Defines one or more packages that contain application-specific resources and
* providers.
*
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
index f4fe50c..82ab90b 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
@@ -16,8 +16,21 @@
package org.glassfish.jersey.server.internal;
-import java.util.concurrent.CompletionStage;
+import static java.lang.Boolean.TRUE;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.BiFunction;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
+import javax.ws.rs.JAXRS.Configuration.Builder;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
import javax.ws.rs.core.Application;
import javax.ws.rs.JAXRS;
import javax.ws.rs.JAXRS.Instance;
@@ -25,11 +38,16 @@
import org.glassfish.jersey.internal.AbstractRuntimeDelegate;
import org.glassfish.jersey.message.internal.MessagingBinders;
import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ServerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
/**
* Server-side implementation of JAX-RS {@link javax.ws.rs.ext.RuntimeDelegate}.
- * This overrides the default implementation of {@link javax.ws.rs.ext.RuntimeDelegate} from
- * jersey-common which does not implement {@link #createEndpoint(javax.ws.rs.core.Application, java.lang.Class)}
+ * This overrides the default implementation of
+ * {@link javax.ws.rs.ext.RuntimeDelegate} from jersey-common which does not
+ * implement
+ * {@link #createEndpoint(javax.ws.rs.core.Application, java.lang.Class)}
* method.
*
* @author Jakub Podlesak
@@ -43,7 +61,7 @@
}
@Override
- public <T> T createEndpoint(Application application, Class<T> endpointType)
+ public <T> T createEndpoint(final Application application, final Class<T> endpointType)
throws IllegalArgumentException, UnsupportedOperationException {
if (application == null) {
throw new IllegalArgumentException("application is null.");
@@ -51,14 +69,105 @@
return ContainerFactory.createContainer(endpointType, application);
}
- @Override
- public JAXRS.Configuration.Builder createConfigurationBuilder() {
- throw new UnsupportedOperationException();
+ private static final Map<String, Class<?>> PROPERTY_TYPES = new HashMap<>();
+
+ static {
+ PROPERTY_TYPES.put(JAXRS.Configuration.PROTOCOL, String.class);
+ PROPERTY_TYPES.put(JAXRS.Configuration.HOST, String.class);
+ PROPERTY_TYPES.put(JAXRS.Configuration.PORT, Integer.class);
+ PROPERTY_TYPES.put(JAXRS.Configuration.ROOT_PATH, String.class);
+ PROPERTY_TYPES.put(JAXRS.Configuration.SSL_CONTEXT, SSLContext.class);
+ PROPERTY_TYPES.put(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION, SSLClientAuthentication.class);
+ PROPERTY_TYPES.put(ServerProperties.HTTP_SERVER_CLASS, Class.class);
+ PROPERTY_TYPES.put(ServerProperties.AUTO_START, Boolean.class);
}
@Override
- public CompletionStage<Instance> bootstrap(Application application, JAXRS.Configuration configuration) {
- throw new UnsupportedOperationException();
+ public JAXRS.Configuration.Builder createConfigurationBuilder() {
+ return new JAXRS.Configuration.Builder() {
+ private final Map<String, Object> properties = new HashMap<>();
+
+ {
+ this.properties.put(JAXRS.Configuration.PROTOCOL, "HTTP");
+ this.properties.put(JAXRS.Configuration.HOST, "localhost");
+ this.properties.put(JAXRS.Configuration.PORT, -1); // Auto-select port 80 for HTTP or 443 for HTTPS
+ this.properties.put(JAXRS.Configuration.ROOT_PATH, "/");
+ this.properties.put(ServerProperties.HTTP_SERVER_CLASS, Server.class); // Auto-select first provider
+ try {
+ this.properties.put(JAXRS.Configuration.SSL_CONTEXT, SSLContext.getDefault());
+ } catch (final NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ this.properties.put(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION,
+ JAXRS.Configuration.SSLClientAuthentication.NONE);
+ this.properties.put(ServerProperties.AUTO_START, TRUE);
+ }
+
+ @Override
+ public final JAXRS.Configuration.Builder property(final String name, final Object value) {
+ this.properties.put(name, value);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final <T> Builder from(final BiFunction<String, Class<T>, Optional<T>> configProvider) {
+ PROPERTY_TYPES.forEach(
+ (propertyName, propertyType) -> configProvider.apply(propertyName, (Class<T>) propertyType)
+ .ifPresent(propertyValue -> this.properties.put(propertyName, propertyValue)));
+ return this;
+ }
+
+ @Override
+ public final JAXRS.Configuration build() {
+ return this.properties::get;
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public CompletableFuture<JAXRS.Instance> bootstrap(final Application application,
+ final JAXRS.Configuration configuration) {
+ return CompletableFuture.supplyAsync(() -> {
+ final Class<Server> httpServerClass = (Class<Server>) configuration
+ .property(ServerProperties.HTTP_SERVER_CLASS);
+
+ return new JAXRS.Instance() {
+ private final Server server = ServerFactory.createServer(httpServerClass, application, configuration);
+
+ @Override
+ public final Configuration configuration() {
+ return name -> {
+ switch (name) {
+ case JAXRS.Configuration.PORT:
+ return server.port();
+ case ServerProperties.HTTP_SERVER_CLASS:
+ return server.getClass();
+ default:
+ return configuration.property(name);
+ }
+ };
+ }
+
+ @Override
+ public final CompletionStage<StopResult> stop() {
+ return this.server.stop().thenApply(nativeResult -> new StopResult() {
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.cast(nativeResult);
+ }
+ });
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return nativeClass.isInstance(this.server) ? nativeClass.cast(this.server)
+ : this.server.unwrap(nativeClass);
+ }
+ };
+ });
}
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java b/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java
new file mode 100644
index 0000000..71d9750
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server.spi;
+
+import java.util.concurrent.CompletionStage;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Jersey service contract for self-contained servers.
+ * <p>
+ * Runs a self-contained {@link Application} in a {@link Container} using an
+ * HTTP server implicitly started and stopped together with the application.
+ * </p>
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface Server {
+
+ /**
+ * @return container in which the application lives.
+ */
+ public Container container();
+
+ /**
+ * @return IP port the application listens to for requests.
+ */
+ public int port();
+
+ /**
+ * Initiates server bootstrap.
+ * <p>
+ * Startup happens in background. The completion stage produces a native startup
+ * result.
+ * </p>
+ * <p>
+ * Portable applications should not expect any particular result type, as it is
+ * implementation-specific.
+ * </p>
+ *
+ * @return A {@link CompletionStage} providing a native startup result of the
+ * bootstrap process. The native result MAY be {@code null}.
+ */
+ public CompletionStage<?> start();
+
+ /**
+ * Initiates server shutdown.
+ * <p>
+ * Shutdown happens in background. The completion stage produces a native
+ * shutdown result.
+ * </p>
+ * </p>
+ * Portable applications should not expect any particular result type, as it is
+ * implementation-specific.
+ * </p>
+ *
+ * @return A {@link CompletionStage} providing a native shutdown result of the
+ * shutdown process. The native result MAY be {@code null}.
+ */
+ public CompletionStage<?> stop();
+
+ /**
+ * Provides access to the native handle(s) of the server, if it holds at least
+ * one.
+ * <p>
+ * Implementations MAY use native handles to identify the server instance, and /
+ * or use those to communicate with and control the instance. Whether or not
+ * such handles exist, and their respective data types, is
+ * implementation-specific.
+ * </p>
+ * <p>
+ * Portable applications should not invoke this method, as the types of
+ * supported handles are implementation-specific.
+ * </p>
+ *
+ * @param nativeClass
+ * The class of the native handle.
+ * @return The native handle, or {@code null} if no handle of this type exists.
+ */
+ public <T> T unwrap(Class<T> nativeClass);
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java
new file mode 100644
index 0000000..f0dd87d
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server.spi;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Service-provider interface for creating server instances.
+ *
+ * If supported by the provider, a server instance of the requested Java type
+ * will be created.
+ * <p>
+ * The created server uses an internally created {@link Container} which is
+ * responsible for listening on a communication channel provided by the server
+ * for new client requests, dispatching these requests to the registered
+ * {@link ApplicationHandler Jersey application handler} using the handler's
+ * {@link ApplicationHandler#handle(org.glassfish.jersey.server.ContainerRequest)
+ * handle(requestContext)} method and sending the responses provided by the
+ * application back to the client.
+ * </p>
+ * <p>
+ * A provider shall support a one-to-one mapping between a type, provided the
+ * type is not {@link Object}. A provider may also support mapping of sub-types
+ * of a type (provided the type is not {@code Object}). It is expected that each
+ * provider supports mapping for distinct set of types and subtypes so that
+ * different providers do not conflict with each other. In addition, a provider
+ * SHOULD support the super type {@link Server} to participate in auto-selection
+ * of providers (in this case the <em>first</em> supporting provider found is
+ * used).
+ * </p>
+ * <p>
+ * An implementation can identify itself by placing a Java service provider
+ * configuration file (if not already present) -
+ * {@code org.glassfish.jersey.server.spi.ServerProvider} - in the resource
+ * directory {@code META-INF/services}, and adding the fully qualified
+ * service-provider-class of the implementation in the file.
+ * </p>
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface ServerProvider {
+
+ /**
+ * Creates a server of a given type which runs the given application using the
+ * given bootstrap configuration.
+ *
+ * @param <T>
+ * the type of the server.
+ * @param type
+ * the type of the server. Providers SHOULD support at least
+ * {@link Server}.
+ * @param application
+ * The application to host.
+ * @param configuration
+ * The configuration (host, port, etc.) to be used for bootstrapping.
+ * @return the server, otherwise {@code null} if the provider does not support
+ * the requested {@code type}.
+ * @throws ProcessingException
+ * if there is an error creating the server.
+ */
+ public <T extends Server> T createServer(Class<T> type, Application application, JAXRS.Configuration configuration)
+ throws ProcessingException;
+}
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java b/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java
new file mode 100644
index 0000000..9033318
--- /dev/null
+++ b/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018 Markus KARG. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://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: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.theInstance;
+import static org.junit.Assert.assertThat;
+
+import java.util.Iterator;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
+import org.glassfish.jersey.internal.guava.Iterators;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManagerFactory;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import mockit.Mocked;
+import mockit.integration.junit4.JMockit;
+
+/**
+ * Unit tests for {@link ServerFactory}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@RunWith(JMockit.class)
+public final class ServerFactoryTest {
+
+ @Test
+ public final void shouldBuildServer(@Mocked final Application mockApplication, @Mocked final Server mockServer,
+ @Mocked final JAXRS.Configuration mockConfiguration, @Mocked final InjectionManager mockInjectionManager) {
+ // given
+ ServiceFinder.setIteratorProvider(new ServiceIteratorProvider() {
+ @Override
+ public final <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
+ final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+ return Iterators.singletonIterator(service.cast(
+ service == ServerProvider.class ? new ServerProvider() {
+ @Override
+ public final <U extends Server> U createServer(final Class<U> type, final Application application,
+ final Configuration configuration) {
+ return application == mockApplication && configuration == mockConfiguration
+ ? type.cast(mockServer)
+ : null;
+ }
+ }
+ : service == InjectionManagerFactory.class ? new InjectionManagerFactory() {
+ @Override
+ public final InjectionManager create(final Object parent) {
+ return mockInjectionManager;
+ }
+ }
+ : null));
+ }
+
+ @Override
+ public final <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final String serviceName,
+ final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+ return null;
+ }
+ });
+
+ // when
+ final Server server = ServerFactory.createServer(Server.class, mockApplication, mockConfiguration);
+
+ // then
+ assertThat(server, is(theInstance(mockServer)));
+ }
+
+ @After
+ public final void resetServiceFinder() {
+ ServiceFinder.setIteratorProvider(null);
+ }
+
+}
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
index 57d9668..2af3eb2 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
@@ -16,18 +16,51 @@
package org.glassfish.jersey.server.internal;
+import static java.lang.Boolean.FALSE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.theInstance;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.RuntimeDelegate;
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.After;
import org.junit.Test;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
+import org.junit.runner.RunWith;
+
+import mockit.Mocked;
+import mockit.integration.junit4.JMockit;
/**
- * Unit test that checks that the right RuntimeDelegateImpl is loaded by JAX-RS.
+ * Unit tests for {@link RuntimeDelegate}.
*
* @author Martin Matula
+ * @author Markus KARG (markus@headcrashing.eu)
*/
+@RunWith(JMockit.class)
public class RuntimeDelegateImplTest {
@Test
@@ -43,8 +76,260 @@
}
}
+ /**
+ * Checks that the right RuntimeDelegateImpl is loaded by JAX-RS.
+ */
@Test
public void testRuntimeDelegateInstance() {
assertSame(RuntimeDelegateImpl.class, RuntimeDelegate.getInstance().getClass());
}
+
+ @Test
+ public final void shouldCreateConfigurationBuilder() {
+ // given
+ final RuntimeDelegate runtimeDelegate = new RuntimeDelegateImpl();
+
+ // when
+ final JAXRS.Configuration.Builder configurationBuilder = runtimeDelegate.createConfigurationBuilder();
+
+ // then
+ assertThat(configurationBuilder, is(notNullValue()));
+ }
+
+ @Test
+ public final void shouldBuildDefaultConfiguration() throws NoSuchAlgorithmException {
+ // given
+ final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+ // when
+ final JAXRS.Configuration configuration = configurationBuilder.build();
+
+ // then
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.property(JAXRS.Configuration.PROTOCOL), is("HTTP"));
+ assertThat(configuration.property(JAXRS.Configuration.HOST), is("localhost"));
+ assertThat(configuration.property(JAXRS.Configuration.PORT), is(JAXRS.Configuration.DEFAULT_PORT));
+ assertThat(configuration.property(JAXRS.Configuration.ROOT_PATH), is("/"));
+ assertThat(configuration.property(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION),
+ is(JAXRS.Configuration.SSLClientAuthentication.NONE));
+ assertThat(configuration.property(JAXRS.Configuration.SSL_CONTEXT), is(theInstance(SSLContext.getDefault())));
+ assertThat(configuration.protocol(), is("HTTP"));
+ assertThat(configuration.host(), is("localhost"));
+ assertThat(configuration.port(), is(JAXRS.Configuration.DEFAULT_PORT));
+ assertThat(configuration.rootPath(), is("/"));
+ assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.NONE));
+ assertThat(configuration.sslContext(), is(theInstance(SSLContext.getDefault())));
+ }
+
+ @Test
+ public final void shouldBuildConfigurationContainingCustomProperties() {
+ // given
+ final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+ // when
+ final JAXRS.Configuration configuration = configurationBuilder.property("property", "value").build();
+
+ // then
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.property("property"), is("value"));
+ }
+
+ @Test
+ public final void shouldBuildCustomConfigurationUsingNamedStandardProperties(@Mocked final SSLContext mockSslContext)
+ throws NoSuchAlgorithmException {
+ // given
+ final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+ // when
+ final JAXRS.Configuration configuration = configurationBuilder.property(JAXRS.Configuration.PROTOCOL, "HTTPS")
+ .property(JAXRS.Configuration.HOST, "hostname").property(JAXRS.Configuration.PORT, 8080)
+ .property(JAXRS.Configuration.ROOT_PATH, "path")
+ .property(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION,
+ JAXRS.Configuration.SSLClientAuthentication.OPTIONAL)
+ .property(JAXRS.Configuration.SSL_CONTEXT, mockSslContext).build();
+
+ // then
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.protocol(), is("HTTPS"));
+ assertThat(configuration.host(), is("hostname"));
+ assertThat(configuration.port(), is(8080));
+ assertThat(configuration.rootPath(), is("path"));
+ assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+ assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+ }
+
+ @Test
+ public final void shouldBuildCustomConfigurationUsingConvenienceMethods(@Mocked final SSLContext mockSslContext)
+ throws NoSuchAlgorithmException {
+ // given
+ final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+ // when
+ final JAXRS.Configuration configuration = configurationBuilder.protocol("HTTPS").host("hostname").port(8080)
+ .rootPath("path").sslClientAuthentication(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL)
+ .sslContext(mockSslContext).build();
+
+ // then
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.protocol(), is("HTTPS"));
+ assertThat(configuration.host(), is("hostname"));
+ assertThat(configuration.port(), is(8080));
+ assertThat(configuration.rootPath(), is("path"));
+ assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+ assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+ }
+
+ @Test
+ public final void shouldBuildCustomConfigurationFromPropertiesProvider(@Mocked final SSLContext mockSslContext) {
+ // given
+ final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+ final Class<Server> mockServerClass = Server.class;
+ final BiFunction<String, Class<Object>, Optional<Object>> propertiesProvider = (propertyName, propertyType) -> {
+ if (JAXRS.Configuration.PROTOCOL.equals(propertyName) && String.class.equals(propertyType)) {
+ return Optional.of("HTTPS");
+ }
+ if (JAXRS.Configuration.HOST.equals(propertyName) && String.class.equals(propertyType)) {
+ return Optional.of("hostname");
+ }
+ if (JAXRS.Configuration.PORT.equals(propertyName) && Integer.class.equals(propertyType)) {
+ return Optional.of(8080);
+ }
+ if (JAXRS.Configuration.ROOT_PATH.equals(propertyName) && String.class.equals(propertyType)) {
+ return Optional.of("path");
+ }
+ if (JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION.equals(propertyName)
+ && JAXRS.Configuration.SSLClientAuthentication.class.equals(propertyType)) {
+ return Optional.of(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL);
+ }
+ if (JAXRS.Configuration.SSL_CONTEXT.equals(propertyName) && SSLContext.class.equals(propertyType)) {
+ return Optional.of(mockSslContext);
+ }
+ if (ServerProperties.HTTP_SERVER_CLASS.equals(propertyName) && Class.class.equals(propertyType)) {
+ return Optional.of(mockServerClass);
+ }
+ if (ServerProperties.AUTO_START.equals(propertyName) && Boolean.class.equals(propertyType)) {
+ return Optional.of(FALSE);
+ }
+ return Optional.empty();
+ };
+
+ // when
+ final JAXRS.Configuration configuration = configurationBuilder.from(propertiesProvider).build();
+
+ // then
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.protocol(), is("HTTPS"));
+ assertThat(configuration.host(), is("hostname"));
+ assertThat(configuration.port(), is(8080));
+ assertThat(configuration.rootPath(), is("path"));
+ assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+ assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+ assertThat(configuration.property(ServerProperties.HTTP_SERVER_CLASS), is(theInstance(mockServerClass)));
+ assertThat(configuration.property(ServerProperties.AUTO_START), is(FALSE));
+ }
+
+ @Test
+ public final void shouldBootstrapApplication(@Mocked final Container mockContainer,
+ @Mocked final Application mockApplication, @Mocked final SSLContext mockSslContext) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ // given
+ final Server mockServer = new Server() {
+ @Override
+ public final Container container() {
+ return mockContainer;
+ }
+
+ @Override
+ public final int port() {
+ return 8888;
+ }
+
+ @Override
+ public final CompletionStage<?> start() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public final CompletionStage<?> stop() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public final <T> T unwrap(final Class<T> nativeClass) {
+ return null;
+ }
+ };
+ final RuntimeDelegate runtimeDelegate = new RuntimeDelegateImpl();
+ final JAXRS.Configuration mockConfiguration = name -> {
+ switch (name) {
+ case JAXRS.Configuration.PROTOCOL:
+ return "HTTPS";
+ case JAXRS.Configuration.HOST:
+ return "hostname";
+ case JAXRS.Configuration.PORT:
+ return JAXRS.Configuration.DEFAULT_PORT;
+ case JAXRS.Configuration.ROOT_PATH:
+ return "path";
+ case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+ return JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+ case JAXRS.Configuration.SSL_CONTEXT:
+ return mockSslContext;
+ case ServerProperties.HTTP_SERVER_CLASS:
+ return Server.class;
+ default:
+ return null;
+ }
+ };
+ ServiceFinder.setIteratorProvider(new ServiceIteratorProvider() {
+ @Override
+ public final <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
+ final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+ return Collections.singleton(service.cast(new ServerProvider() {
+ @Override
+ public final <U extends Server> U createServer(final Class<U> type, final Application application,
+ final Configuration configuration) {
+ return application == mockApplication && configuration == mockConfiguration
+ ? type.cast(mockServer)
+ : null;
+ }
+ })).iterator();
+ }
+
+ @Override
+ public final <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final String serviceName,
+ final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+ return null;
+ }
+ });
+
+ // when
+ final CompletionStage<JAXRS.Instance> bootstrapStage = runtimeDelegate.bootstrap(mockApplication,
+ mockConfiguration);
+ final JAXRS.Instance instance = bootstrapStage.toCompletableFuture().get(15, SECONDS);
+ final Configuration configuration = instance.configuration();
+ final Server server = instance.unwrap(Server.class);
+ final Container container = server.container();
+ final CompletionStage<JAXRS.Instance.StopResult> stopStage = instance.stop();
+ final Object stopResult = stopStage.toCompletableFuture().get(15, SECONDS);
+
+ // then
+ assertThat(instance, is(notNullValue()));
+ assertThat(configuration, is(notNullValue()));
+ assertThat(configuration.protocol(), is("HTTPS"));
+ assertThat(configuration.host(), is("hostname"));
+ assertThat(configuration.port(), is(8888));
+ assertThat(configuration.rootPath(), is("path"));
+ assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+ assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+ assertThat(configuration.property(ServerProperties.HTTP_SERVER_CLASS), is(theInstance(mockServer.getClass())));
+ assertThat(server, is(theInstance(mockServer)));
+ assertThat(container, is(theInstance(mockContainer)));
+ assertThat(stopResult, is(notNullValue()));
+ }
+
+ @After
+ public final void resetServiceFinder() {
+ ServiceFinder.setIteratorProvider(null);
+ }
+
}