Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/containers/glassfish/jersey-gf-ejb/pom.xml b/containers/glassfish/jersey-gf-ejb/pom.xml
new file mode 100644
index 0000000..4038ce7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/pom.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers.glassfish</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-gf-ejb</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-gf-ejb</name>
+
+ <description>Jersey EJB for GlassFish integration</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.ejb</groupId>
+ <artifactId>javax.ejb-api</artifactId>
+ <version>${ejb.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.interceptor</groupId>
+ <artifactId>javax.interceptor-api</artifactId>
+ <version>${javax.interceptor.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.main.ejb</groupId>
+ <artifactId>ejb-container</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.main.common</groupId>
+ <artifactId>container-common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+ packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+ -->
+ <excludePackageNames>*.tests.*</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- Explicitly set versions for packages from GlassFish to allow future uptake of GlassFish 5.x-->
+ <Import-Package>
+ com.sun.*;version="[4.0,6)",
+ org.glassfish.ejb.*;version="[4.0,6)",
+ org.glassfish.internal.*;version="[4.0,6)",
+ *
+ </Import-Package>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_nodefaultversion>false</_nodefaultversion>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java
new file mode 100644
index 0000000..26e6e8b
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012, 2018 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.gf.ejb.internal;
+
+import javax.annotation.PostConstruct;
+import javax.interceptor.InvocationContext;
+
+import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * EJB interceptor to inject Jersey specific stuff into EJB beans.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class EjbComponentInterceptor {
+
+ private final InjectionManager injectionManager;
+
+ /**
+ * Create new EJB component injection manager.
+ *
+ * @param injectionManager injection manager.
+ */
+ public EjbComponentInterceptor(final InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+ }
+
+ @PostConstruct
+ private void inject(final InvocationContext context) throws Exception {
+
+ final Object beanInstance = context.getTarget();
+ injectionManager.inject(beanInstance, CdiComponentProvider.CDI_CLASS_ANALYZER);
+
+ // Invoke next interceptor in chain
+ context.proceed();
+ }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
new file mode 100644
index 0000000..8a652e3
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2012, 2018 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.gf.ejb.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.annotation.Priority;
+import javax.ejb.Local;
+import javax.ejb.Remote;
+import javax.inject.Singleton;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Binding;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InstanceBinding;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+import org.glassfish.jersey.server.spi.internal.ResourceMethodInvocationHandlerProvider;
+
+import org.glassfish.ejb.deployment.descriptor.EjbBundleDescriptorImpl;
+import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
+import org.glassfish.internal.data.ApplicationInfo;
+import org.glassfish.internal.data.ApplicationRegistry;
+import org.glassfish.internal.data.ModuleInfo;
+
+import com.sun.ejb.containers.BaseContainer;
+import com.sun.ejb.containers.EjbContainerUtil;
+import com.sun.ejb.containers.EjbContainerUtilImpl;
+import com.sun.enterprise.config.serverbeans.Application;
+import com.sun.enterprise.config.serverbeans.Applications;
+
+/**
+ * EJB component provider.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(300)
+@SuppressWarnings("UnusedDeclaration")
+public final class EjbComponentProvider implements ComponentProvider, ResourceMethodInvocationHandlerProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(EjbComponentProvider.class.getName());
+
+ private InitialContext initialContext;
+ private final List<String> libNames = new CopyOnWriteArrayList<>();
+
+ private boolean ejbInterceptorRegistered = false;
+
+ /**
+ * HK2 factory to provide EJB components obtained via JNDI lookup.
+ */
+ private static class EjbFactory<T> implements Supplier<T> {
+
+ final InitialContext ctx;
+ final Class<T> clazz;
+ final EjbComponentProvider ejbProvider;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get() {
+ try {
+ return (T) lookup(ctx, clazz, clazz.getSimpleName(), ejbProvider);
+ } catch (NamingException ex) {
+ Logger.getLogger(ApplicationHandler.class.getName()).log(Level.SEVERE, null, ex);
+ return null;
+ }
+ }
+
+ public EjbFactory(Class<T> rawType, InitialContext ctx, EjbComponentProvider ejbProvider) {
+ this.clazz = rawType;
+ this.ctx = ctx;
+ this.ejbProvider = ejbProvider;
+ }
+ }
+
+ /**
+ * Annotations to determine EJB components.
+ */
+ private static final Set<String> EjbComponentAnnotations = Collections.unmodifiableSet(new HashSet<String>() {{
+ add("javax.ejb.Stateful");
+ add("javax.ejb.Stateless");
+ add("javax.ejb.Singleton");
+ }});
+
+ private InjectionManager injectionManager = null;
+
+ // ComponentProvider
+ @Override
+ public void initialize(final InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+
+ InstanceBinding<EjbComponentProvider> descriptor = Bindings.service(EjbComponentProvider.this)
+ .to(ResourceMethodInvocationHandlerProvider.class);
+ this.injectionManager.register(descriptor);
+ }
+
+ private ApplicationInfo getApplicationInfo(EjbContainerUtil ejbUtil) throws NamingException {
+ ApplicationRegistry appRegistry = ejbUtil.getServices().getService(ApplicationRegistry.class);
+ Applications applications = ejbUtil.getServices().getService(Applications.class);
+ String appNamePrefix = (String) initialContext.lookup("java:app/AppName");
+ Set<String> appNames = appRegistry.getAllApplicationNames();
+ Set<String> disabledApps = new TreeSet<>();
+ for (String appName : appNames) {
+ if (appName.startsWith(appNamePrefix)) {
+ Application appDesc = applications.getApplication(appName);
+ if (appDesc != null && !ejbUtil.getDeployment().isAppEnabled(appDesc)) {
+ // skip disabled version of the app
+ disabledApps.add(appName);
+ } else {
+ return ejbUtil.getDeployment().get(appName);
+ }
+ }
+ }
+
+ // grab the latest one, there is no way to make
+ // sure which one the user is actually enabling,
+ // so use the best case, i.e. upgrade
+ Iterator<String> it = disabledApps.iterator();
+ String lastDisabledApp = null;
+ while (it.hasNext()) {
+ lastDisabledApp = it.next();
+ }
+ if (lastDisabledApp != null) {
+ return ejbUtil.getDeployment().get(lastDisabledApp);
+ }
+
+ throw new NamingException("Application Information Not Found");
+ }
+
+ private void registerEjbInterceptor() {
+ try {
+ final Object interceptor = new EjbComponentInterceptor(injectionManager);
+ initialContext = getInitialContext();
+ final EjbContainerUtil ejbUtil = EjbContainerUtilImpl.getInstance();
+ final ApplicationInfo appInfo = getApplicationInfo(ejbUtil);
+ final List<String> tempLibNames = new LinkedList<>();
+ for (ModuleInfo moduleInfo : appInfo.getModuleInfos()) {
+ final String jarName = moduleInfo.getName();
+ if (jarName.endsWith(".jar") || jarName.endsWith(".war")) {
+ final String moduleName = jarName.substring(0, jarName.length() - 4);
+ tempLibNames.add(moduleName);
+ final Object bundleDescriptor = moduleInfo.getMetaData(EjbBundleDescriptorImpl.class.getName());
+ if (bundleDescriptor instanceof EjbBundleDescriptorImpl) {
+ final Collection<EjbDescriptor> ejbs = ((EjbBundleDescriptorImpl) bundleDescriptor).getEjbs();
+
+ for (final EjbDescriptor ejb : ejbs) {
+ final BaseContainer ejbContainer = EjbContainerUtilImpl.getInstance().getContainer(ejb.getUniqueId());
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ @Override
+ public Object run() throws Exception {
+ final Method registerInterceptorMethod =
+ BaseContainer.class
+ .getDeclaredMethod("registerSystemInterceptor", java.lang.Object.class);
+ registerInterceptorMethod.setAccessible(true);
+
+ registerInterceptorMethod.invoke(ejbContainer, interceptor);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ final Throwable cause = pae.getCause();
+ LOGGER.log(Level.WARNING,
+ LocalizationMessages.EJB_INTERCEPTOR_BINDING_WARNING(ejb.getEjbClassName()), cause);
+ }
+ }
+ }
+ }
+ }
+ libNames.addAll(tempLibNames);
+ final Object interceptorBinder = initialContext.lookup("java:org.glassfish.ejb.container.interceptor_binding_spi");
+ // Some implementations of InitialContext return null instead of
+ // throwing NamingException if there is no Object associated with
+ // the name
+ if (interceptorBinder == null) {
+ throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_BIND_API_NOT_AVAILABLE());
+ }
+
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ @Override
+ public Object run() throws Exception {
+ Method interceptorBinderMethod = interceptorBinder.getClass()
+ .getMethod("registerInterceptor", java.lang.Object.class);
+
+ interceptorBinderMethod.invoke(interceptorBinder, interceptor);
+ EjbComponentProvider.this.ejbInterceptorRegistered = true;
+ LOGGER.log(Level.CONFIG, LocalizationMessages.EJB_INTERCEPTOR_BOUND());
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_CONFIG_ERROR(), pae.getCause());
+ }
+
+ } catch (NamingException ex) {
+ throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_BIND_API_NOT_AVAILABLE(), ex);
+ } catch (LinkageError ex) {
+ throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_CONFIG_LINKAGE_ERROR(), ex);
+ }
+ }
+
+ // ComponentProvider
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
+
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.fine(LocalizationMessages.EJB_CLASS_BEING_CHECKED(component));
+ }
+
+ if (injectionManager == null) {
+ throw new IllegalStateException(LocalizationMessages.EJB_COMPONENT_PROVIDER_NOT_INITIALIZED_PROPERLY());
+ }
+
+ if (!isEjbComponent(component)) {
+ return false;
+ }
+
+ if (!ejbInterceptorRegistered) {
+ registerEjbInterceptor();
+ }
+
+ Binding binding = Bindings.supplier(new EjbFactory(component, initialContext, EjbComponentProvider.this))
+ .to(component)
+ .to(providerContracts);
+ injectionManager.register(binding);
+
+ if (LOGGER.isLoggable(Level.CONFIG)) {
+ LOGGER.config(LocalizationMessages.EJB_CLASS_BOUND_WITH_CDI(component));
+ }
+
+ return true;
+ }
+
+ @Override
+ public void done() {
+ registerEjbExceptionMapper();
+ }
+
+ private void registerEjbExceptionMapper() {
+ injectionManager.register(new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(EjbExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
+ }
+ });
+ }
+
+ private boolean isEjbComponent(Class<?> component) {
+ for (Annotation a : component.getAnnotations()) {
+ if (EjbComponentAnnotations.contains(a.annotationType().getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public InvocationHandler create(Invocable method) {
+
+ final Class<?> resourceClass = method.getHandler().getHandlerClass();
+
+ if (resourceClass == null || !isEjbComponent(resourceClass)) {
+ return null;
+ }
+
+ final Method handlingMethod = method.getDefinitionMethod();
+
+ for (Class iFace : remoteAndLocalIfaces(resourceClass)) {
+ try {
+ final Method iFaceMethod = iFace.getDeclaredMethod(handlingMethod.getName(), handlingMethod.getParameterTypes());
+ if (iFaceMethod != null) {
+ return new InvocationHandler() {
+ @Override
+ public Object invoke(Object target, Method ignored, Object[] args)
+ throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ return iFaceMethod.invoke(target, args);
+ }
+ };
+ }
+ } catch (NoSuchMethodException | SecurityException ex) {
+ logLookupException(handlingMethod, resourceClass, iFace, ex);
+ }
+ }
+ return null;
+ }
+
+ private void logLookupException(final Method method, final Class<?> component, Class<?> iFace, Exception ex) {
+ LOGGER.log(
+ Level.WARNING,
+ LocalizationMessages.EJB_INTERFACE_HANDLING_METHOD_LOOKUP_EXCEPTION(method, component, iFace), ex);
+ }
+
+ private List<Class> remoteAndLocalIfaces(final Class<?> resourceClass) {
+ final List<Class> allLocalOrRemoteIfaces = new LinkedList<>();
+ if (resourceClass.isAnnotationPresent(Remote.class)) {
+ allLocalOrRemoteIfaces.addAll(Arrays.asList(resourceClass.getAnnotation(Remote.class).value()));
+ }
+ if (resourceClass.isAnnotationPresent(Local.class)) {
+ allLocalOrRemoteIfaces.addAll(Arrays.asList(resourceClass.getAnnotation(Local.class).value()));
+ }
+ for (Class<?> i : resourceClass.getInterfaces()) {
+ if (i.isAnnotationPresent(Remote.class) || i.isAnnotationPresent(Local.class)) {
+ allLocalOrRemoteIfaces.add(i);
+ }
+ }
+ return allLocalOrRemoteIfaces;
+ }
+
+ private static InitialContext getInitialContext() {
+ try {
+ // Deployment on Google App Engine will
+ // result in a LinkageError
+ return new InitialContext();
+ } catch (Exception ex) {
+ throw new IllegalStateException(LocalizationMessages.INITIAL_CONTEXT_NOT_AVAILABLE(), ex);
+ }
+ }
+
+ private static Object lookup(InitialContext ic, Class<?> c, String name, EjbComponentProvider provider)
+ throws NamingException {
+ try {
+ return lookupSimpleForm(ic, name, provider);
+ } catch (NamingException ex) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.EJB_CLASS_SIMPLE_LOOKUP_FAILED(c.getName()), ex);
+
+ return lookupFullyQualifiedForm(ic, c, name, provider);
+ }
+ }
+
+ private static Object lookupSimpleForm(InitialContext ic, String name, EjbComponentProvider provider) throws NamingException {
+ if (provider.libNames.isEmpty()) {
+ String jndiName = "java:module/" + name;
+ return ic.lookup(jndiName);
+ } else {
+ NamingException ne = null;
+ for (String moduleName : provider.libNames) {
+ String jndiName = "java:app/" + moduleName + "/" + name;
+ Object result;
+ try {
+ result = ic.lookup(jndiName);
+ if (result != null) {
+ return result;
+ }
+ } catch (NamingException e) {
+ ne = e;
+ }
+ }
+ throw (ne != null) ? ne : new NamingException();
+ }
+ }
+
+ private static Object lookupFullyQualifiedForm(InitialContext ic, Class<?> c, String name, EjbComponentProvider provider)
+ throws NamingException {
+ if (provider.libNames.isEmpty()) {
+ String jndiName = "java:module/" + name + "!" + c.getName();
+ return ic.lookup(jndiName);
+ } else {
+ NamingException ne = null;
+ for (String moduleName : provider.libNames) {
+ String jndiName = "java:app/" + moduleName + "/" + name + "!" + c.getName();
+ Object result;
+ try {
+ result = ic.lookup(jndiName);
+ if (result != null) {
+ return result;
+ }
+ } catch (NamingException e) {
+ ne = e;
+ }
+ }
+ throw (ne != null) ? ne : new NamingException();
+ }
+ }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java
new file mode 100644
index 0000000..1121c41
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2012, 2018 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.gf.ejb.internal;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.ejb.EJBException;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.spi.ExceptionMappers;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Helper class to handle exceptions wrapped by the EJB container with EJBException.
+ * If this mapper was not registered, no {@link WebApplicationException}
+ * would end up mapped to the corresponding response.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class EjbExceptionMapper implements ExtendedExceptionMapper<EJBException> {
+
+ private final Provider<ExceptionMappers> mappers;
+
+ /**
+ * Create new EJB exception mapper.
+ *
+ * @param mappers utility to find mapper delegate.
+ */
+ @Inject
+ public EjbExceptionMapper(Provider<ExceptionMappers> mappers) {
+ this.mappers = mappers;
+ }
+
+ @Override
+ public Response toResponse(EJBException exception) {
+ return causeToResponse(exception);
+ }
+
+ @Override
+ public boolean isMappable(EJBException exception) {
+ try {
+ return (causeToResponse(exception) != null);
+ } catch (Throwable ignored) {
+ return false;
+ }
+ }
+
+ private Response causeToResponse(EJBException exception) {
+
+ final Exception cause = exception.getCausedByException();
+
+ if (cause != null) {
+
+ final ExceptionMapper mapper = mappers.get().findMapping(cause);
+ if (mapper != null && mapper != this) {
+
+ return mapper.toResponse(cause);
+
+ } else if (cause instanceof WebApplicationException) {
+
+ return ((WebApplicationException) cause).getResponse();
+ }
+ }
+ return null;
+ }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java
new file mode 100644
index 0000000..700cfe7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 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 internal package supporting Jersey EJB injections in Glassfish 4 environment.
+ */
+package org.glassfish.jersey.gf.ejb.internal;
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..2db13b7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.gf.ejb.internal.EjbComponentProvider
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties b/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties
new file mode 100644
index 0000000..95bbf1c
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2012, 2018 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
+#
+
+initial.context.not.available=InitialContext not found. JAX-RS EJB support is not available.
+ejb.interceptor.bind.api.not.available=The EJB interceptor binding API is not available. JAX-RS EJB integration can not be supported.
+ejb.interceptor.bind.api.non.conformant=The EJB interceptor binding API does not conform to what is expected. JAX-RS EJB integration can not be supported.
+ejb.interceptor.binding.warning=Could not bind EJB intercetor for class, {0}.
+ejb.interceptor.bound=The Jersey EJB interceptor is bound. JAX-RS EJB integration support is enabled.
+ejb.interceptor.config.error=Error when configuring to use the EJB interceptor binding API. JAX-RS EJB integration can not be supported.
+ejb.interceptor.config.security.error=Security issue when configuring to use the EJB interceptor binding API. JAX-RS EJB support is not available.
+ejb.interceptor.config.linkage.error=Linkage error when configuring to use the EJB interceptor binding API. JAX-RS EJB integration can not be supported.
+ejb.component.provider.not.initialized.properly=EJB component provider has not been initialized properly.
+ejb.class.simple.lookup.failed=An instance of EJB class, {0}, could not be looked up using simple form name. Attempting to look up using the fully-qualified form name.
+ejb.interface.handling.method.lookup.exception=Exception thrown when trying to lookup actual handling method, {0}, for EJB type, {1}, using interface {2}.
+ejb.class.being.checked=Class, {0}, is being checked with Jersey EJB component provider.
+ejb.class.bound.with.cdi=Class, {0}, has been bound by Jersey EJB component provider.
diff --git a/containers/glassfish/pom.xml b/containers/glassfish/pom.xml
new file mode 100644
index 0000000..c10d92e
--- /dev/null
+++ b/containers/glassfish/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.containers.glassfish</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-glassfish-support</name>
+
+ <description>Jersey GlassFish container providers umbrella project module</description>
+
+ <modules>
+ <module>jersey-gf-ejb</module>
+ </modules>
+
+</project>
diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
new file mode 100644
index 0000000..49e75e5
--- /dev/null
+++ b/containers/grizzly2-http/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-grizzly2-http</name>
+
+ <description>Grizzly 2 Http Container.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-http-server</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+</project>
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java
new file mode 100644
index 0000000..8c9815d
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2010, 2018 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.grizzly2.httpserver;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.process.internal.RequestScoped;
+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.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import org.glassfish.grizzly.CompletionHandler;
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.Request;
+import org.glassfish.grizzly.http.server.Response;
+
+/**
+ * Jersey {@code Container} implementation based on Grizzly {@link org.glassfish.grizzly.http.server.HttpHandler}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class GrizzlyHttpContainer extends HttpHandler implements Container {
+
+ private static final ExtendedLogger logger =
+ new ExtendedLogger(Logger.getLogger(GrizzlyHttpContainer.class.getName()), Level.FINEST);
+
+ private final Type RequestTYPE = (new GenericType<Ref<Request>>() { }).getType();
+ private final Type ResponseTYPE = (new GenericType<Ref<Response>>() { }).getType();
+
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+ * If {@code true} method {@link org.glassfish.grizzly.http.server.Response#setStatus} is used over
+ * {@link org.glassfish.grizzly.http.server.Response#sendError}.
+ */
+ private boolean configSetStatusOverSendError;
+
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#REDUCE_CONTEXT_PATH_SLASHES_ENABLED}.
+ * If {@code true} method {@link org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer#getRequestUri(Request)}
+ * will reduce the of leading context-path slashes to only one.
+ */
+ private boolean configReduceContextPathSlashesEnabled;
+
+ /**
+ * Referencing factory for Grizzly request.
+ */
+ private static class GrizzlyRequestReferencingFactory extends ReferencingFactory<Request> {
+
+ @Inject
+ public GrizzlyRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * Referencing factory for Grizzly response.
+ */
+ private static class GrizzlyResponseReferencingFactory extends ReferencingFactory<Response> {
+
+ @Inject
+ public GrizzlyResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * An internal binder to enable Grizzly HTTP container specific types injection.
+ * <p/>
+ * This binder allows to inject underlying Grizzly HTTP request and response instances.
+ * Note that since Grizzly {@code Request} class is not proxiable as it does not expose an empty constructor,
+ * the injection of Grizzly request instance into singleton JAX-RS and Jersey providers is only supported via
+ * {@link javax.inject.Provider injection provider}.
+ */
+ static class GrizzlyBinder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bindFactory(GrizzlyRequestReferencingFactory.class).to(Request.class)
+ .proxy(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Request>referenceFactory()).to(new GenericType<Ref<Request>>() {})
+ .in(RequestScoped.class);
+
+ bindFactory(GrizzlyResponseReferencingFactory.class).to(Response.class)
+ .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Response>referenceFactory()).to(new GenericType<Ref<Response>>() {})
+ .in(RequestScoped.class);
+ }
+ }
+
+ private static final CompletionHandler<Response> EMPTY_COMPLETION_HANDLER = new CompletionHandler<Response>() {
+
+ @Override
+ public void cancelled() {
+ // no-op
+ }
+
+ @Override
+ public void failed(final Throwable throwable) {
+ // no-op
+ }
+
+ @Override
+ public void completed(final Response result) {
+ // no-op
+ }
+
+ @Override
+ public void updated(final Response result) {
+ // no-op
+ }
+ };
+
+ private static final class ResponseWriter implements ContainerResponseWriter {
+
+ private final String name;
+ private final Response grizzlyResponse;
+ private final boolean configSetStatusOverSendError;
+
+ ResponseWriter(final Response response, final boolean configSetStatusOverSendError) {
+ this.grizzlyResponse = response;
+ this.configSetStatusOverSendError = configSetStatusOverSendError;
+
+ if (logger.isDebugLoggable()) {
+ this.name = "ResponseWriter {" + "id=" + UUID.randomUUID().toString() + ", grizzlyResponse="
+ + grizzlyResponse.hashCode() + '}';
+ logger.debugLog("{0} - init", name);
+ } else {
+ this.name = "ResponseWriter";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public void commit() {
+ try {
+ if (grizzlyResponse.isSuspended()) {
+ grizzlyResponse.resume();
+ }
+ } finally {
+ logger.debugLog("{0} - commit() called", name);
+ }
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+ try {
+ grizzlyResponse.suspend(timeOut, timeUnit, EMPTY_COMPLETION_HANDLER,
+ new org.glassfish.grizzly.http.server.TimeoutHandler() {
+
+ @Override
+ public boolean onTimeout(final Response response) {
+ if (timeoutHandler != null) {
+ timeoutHandler.onTimeout(ResponseWriter.this);
+ }
+
+ // TODO should we return true in some cases instead?
+ // Returning false relies on the fact that the timeoutHandler will resume the response.
+ return false;
+ }
+ }
+ );
+ return true;
+ } catch (final IllegalStateException ex) {
+ return false;
+ } finally {
+ logger.debugLog("{0} - suspend(...) called", name);
+ }
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+ try {
+ grizzlyResponse.getSuspendContext().setTimeout(timeOut, timeUnit);
+ } finally {
+ logger.debugLog("{0} - setTimeout(...) called", name);
+ }
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(final long contentLength,
+ final ContainerResponse context)
+ throws ContainerException {
+ try {
+ final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+ if (statusInfo.getReasonPhrase() == null) {
+ grizzlyResponse.setStatus(statusInfo.getStatusCode());
+ } else {
+ grizzlyResponse.setStatus(statusInfo.getStatusCode(), statusInfo.getReasonPhrase());
+ }
+
+ grizzlyResponse.setContentLengthLong(contentLength);
+
+ for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+ for (final String value : e.getValue()) {
+ grizzlyResponse.addHeader(e.getKey(), value);
+ }
+ }
+
+ return grizzlyResponse.getOutputStream();
+ } finally {
+ logger.debugLog("{0} - writeResponseStatusAndHeaders() called", name);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("MagicNumber")
+ public void failure(final Throwable error) {
+ try {
+ if (!grizzlyResponse.isCommitted()) {
+ try {
+ if (configSetStatusOverSendError) {
+ grizzlyResponse.reset();
+ grizzlyResponse.setStatus(500, "Request failed.");
+ } else {
+ grizzlyResponse.sendError(500, "Request failed.");
+ }
+ } catch (final IllegalStateException ex) {
+ // a race condition externally committing the response can still occur...
+ logger.log(Level.FINER, "Unable to reset failed response.", ex);
+ } catch (final IOException ex) {
+ throw new ContainerException(
+ LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(500, "Request failed."),
+ ex);
+ }
+ }
+ } finally {
+ logger.debugLog("{0} - failure(...) called", name);
+ rethrow(error);
+ }
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true;
+ }
+
+ /**
+ * Rethrow the original exception as required by JAX-RS, 3.3.4
+ *
+ * @param error throwable to be re-thrown
+ */
+ private void rethrow(final Throwable error) {
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+ }
+
+ private volatile ApplicationHandler appHandler;
+
+ /**
+ * Create a new Grizzly HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Grizzly HTTP container.
+ */
+ /* package */ GrizzlyHttpContainer(final Application application) {
+ this.appHandler = new ApplicationHandler(application, new GrizzlyBinder());
+ cacheConfigSetStatusOverSendError();
+ cacheConfigEnableLeadingContextPathSlashes();
+ }
+
+ /**
+ * Create a new Grizzly HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Grizzly HTTP container.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ */
+ /* package */ GrizzlyHttpContainer(final Application application, final Object parentContext) {
+ this.appHandler = new ApplicationHandler(application, new GrizzlyBinder(), parentContext);
+ cacheConfigSetStatusOverSendError();
+ cacheConfigEnableLeadingContextPathSlashes();
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ appHandler.onStartup(this);
+ }
+
+ @Override
+ public void service(final Request request, final Response response) {
+ final ResponseWriter responseWriter = new ResponseWriter(response, configSetStatusOverSendError);
+ try {
+ logger.debugLog("GrizzlyHttpContainer.service(...) started");
+ URI baseUri = getBaseUri(request);
+ URI requestUri = getRequestUri(request);
+ final ContainerRequest requestContext = new ContainerRequest(baseUri,
+ requestUri, request.getMethod().getMethodString(),
+ getSecurityContext(request), new GrizzlyRequestPropertiesDelegate(request));
+ requestContext.setEntityStream(request.getInputStream());
+ for (final String headerName : request.getHeaderNames()) {
+ requestContext.headers(headerName, request.getHeaders(headerName));
+ }
+ requestContext.setWriter(responseWriter);
+
+ requestContext.setRequestScopedInitializer(injectionManager -> {
+ injectionManager.<Ref<Request>>getInstance(RequestTYPE).set(request);
+ injectionManager.<Ref<Response>>getInstance(ResponseTYPE).set(response);
+ });
+ appHandler.handle(requestContext);
+ } finally {
+ logger.debugLog("GrizzlyHttpContainer.service(...) finished");
+ }
+ }
+
+ private boolean containsContextPath(Request request) {
+ return request.getContextPath() != null && request.getContextPath().length() > 0;
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(appHandler.getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration, new GrizzlyBinder());
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ cacheConfigSetStatusOverSendError();
+ cacheConfigEnableLeadingContextPathSlashes();
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ appHandler.onShutdown(this);
+ appHandler = null;
+ }
+
+ private SecurityContext getSecurityContext(final Request request) {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.isSecure();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return request.getUserPrincipal();
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return request.getAuthType();
+ }
+ };
+ }
+
+ private URI getBaseUri(final Request request) {
+ try {
+ return new URI(request.getScheme(), null, request.getServerName(),
+ request.getServerPort(), getBasePath(request), null, null);
+ } catch (final URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getBasePath(final Request request) {
+ final String contextPath = request.getContextPath();
+
+ if (contextPath == null || contextPath.isEmpty()) {
+ return "/";
+ } else if (contextPath.charAt(contextPath.length() - 1) != '/') {
+ return contextPath + "/";
+ } else {
+ return contextPath;
+ }
+ }
+
+ private URI getRequestUri(final Request request) {
+ try {
+ final String serverAddress = getServerAddress(request);
+
+ String uri;
+ if (configReduceContextPathSlashesEnabled && containsContextPath(request)) {
+ uri = ContainerUtils.reduceLeadingSlashes(request.getRequestURI());
+ } else {
+ uri = request.getRequestURI();
+ }
+
+ final String queryString = request.getQueryString();
+ if (queryString != null) {
+ uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+ }
+
+ return new URI(serverAddress + uri);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getServerAddress(final Request request) throws URISyntaxException {
+ return new URI(request.getScheme(), null, request.getServerName(), request.getServerPort(), null, null, null).toString();
+ }
+
+ /**
+ * The method reads and caches value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR} for future purposes.
+ */
+ private void cacheConfigSetStatusOverSendError() {
+ this.configSetStatusOverSendError = ServerProperties.getValue(getConfiguration().getProperties(),
+ ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+ }
+
+ /**
+ * The method reads and caches value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#REDUCE_CONTEXT_PATH_SLASHES_ENABLED} for future purposes.
+ */
+ private void cacheConfigEnableLeadingContextPathSlashes() {
+ this.configReduceContextPathSlashesEnabled = ServerProperties.getValue(getConfiguration().getProperties(),
+ ServerProperties.REDUCE_CONTEXT_PATH_SLASHES_ENABLED, false, Boolean.class);
+ }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java
new file mode 100644
index 0000000..4d14227
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011, 2018 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.grizzly2.httpserver;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.glassfish.grizzly.http.server.HttpHandler;
+
+/**
+ * Container provider for containers based on Grizzly {@link org.glassfish.grizzly.http.server.HttpHandler}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class GrizzlyHttpContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+ if (HttpHandler.class == type || GrizzlyHttpContainer.class == type) {
+ return type.cast(new GrizzlyHttpContainer(application));
+ }
+
+ return null;
+ }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
new file mode 100644
index 0000000..fed3ec7
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2010, 2018 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.grizzly2.httpserver;
+
+import java.io.IOException;
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+
+import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.HttpHandlerRegistration;
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.http.server.NetworkListener;
+import org.glassfish.grizzly.http.server.ServerConfiguration;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.grizzly.utils.Charsets;
+
+/**
+ * Factory for creating Grizzly Http Server.
+ * <p>
+ * Should you need to fine tune the underlying Grizzly transport layer, you can obtain direct access to the corresponding
+ * Grizzly structures with <tt>server.getListener("grizzly").getTransport()</tt>. To make certain options take effect,
+ * you need to work with an inactive HttpServer instance (that is the one that has not been started yet).
+ * To obtain such an instance, use one of the below factory methods with {@code start} parameter set to {@code false}.
+ * When the {@code start} parameter is not present, the factory method returns an already started instance.
+ * </p>
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @see HttpServer
+ * @see GrizzlyHttpContainer
+ */
+public final class GrizzlyHttpServerFactory {
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path segment will be used as
+ * context path, the rest will be ignored.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri) {
+ return createHttpServer(uri, (GrizzlyHttpContainer) null, false, null, true);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param start if set to false, server will not get started, which allows to configure the underlying transport
+ * layer, see above for details.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri, final boolean start) {
+ return createHttpServer(uri, (GrizzlyHttpContainer) null, false, null, start);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be
+ * used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration) {
+ return createHttpServer(
+ uri,
+ new GrizzlyHttpContainer(configuration),
+ false,
+ null,
+ true
+ );
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be
+ * used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @param start if set to false, server will not get started, which allows to configure the underlying
+ * transport layer, see above for details.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration, final boolean start) {
+ return createHttpServer(
+ uri,
+ new GrizzlyHttpContainer(configuration),
+ false,
+ null,
+ start);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment
+ * will be used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @param secure used for call {@link NetworkListener#setSecure(boolean)}.
+ * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri,
+ final ResourceConfig configuration,
+ final boolean secure,
+ final SSLEngineConfigurator sslEngineConfigurator) {
+ return createHttpServer(
+ uri,
+ new GrizzlyHttpContainer(configuration),
+ secure,
+ sslEngineConfigurator,
+ true);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment
+ * will be used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @param secure used for call {@link NetworkListener#setSecure(boolean)}.
+ * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+ * @param start if set to false, server will not get started, which allows to configure the
+ * underlying transport, see above for details.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ */
+ public static HttpServer createHttpServer(final URI uri,
+ final ResourceConfig configuration,
+ final boolean secure,
+ final SSLEngineConfigurator sslEngineConfigurator,
+ final boolean start) {
+ return createHttpServer(
+ uri,
+ new GrizzlyHttpContainer(configuration),
+ secure,
+ sslEngineConfigurator,
+ start);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @param config web application configuration.
+ * @param secure used for call {@link NetworkListener#setSecure(boolean)}.
+ * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ * @see GrizzlyHttpContainer
+ * @since 2.12
+ */
+ public static HttpServer createHttpServer(final URI uri,
+ final ResourceConfig config,
+ final boolean secure,
+ final SSLEngineConfigurator sslEngineConfigurator,
+ final Object parentContext) {
+ return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), secure, sslEngineConfigurator,
+ true);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @param config web application configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ * @see GrizzlyHttpContainer
+ * @since 2.12
+ */
+ public static HttpServer createHttpServer(final URI uri,
+ final ResourceConfig config,
+ final Object parentContext) {
+ return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), false, null, true);
+ }
+
+ /**
+ * Create new {@link HttpServer} instance.
+ *
+ * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @param handler {@link HttpHandler} instance.
+ * @param secure used for call {@link NetworkListener#setSecure(boolean)}.
+ * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+ * @param start if set to false, server will not get started, this allows end users to set
+ * additional properties on the underlying listener.
+ * @return newly created {@code HttpServer}.
+ * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+ * @see GrizzlyHttpContainer
+ */
+ public static HttpServer createHttpServer(final URI uri,
+ final GrizzlyHttpContainer handler,
+ final boolean secure,
+ final SSLEngineConfigurator sslEngineConfigurator,
+ final boolean start) {
+
+ final String host = (uri.getHost() == null) ? NetworkListener.DEFAULT_NETWORK_HOST : uri.getHost();
+ final int port = (uri.getPort() == -1)
+ ? (secure ? Container.DEFAULT_HTTPS_PORT : Container.DEFAULT_HTTP_PORT)
+ : uri.getPort();
+
+ final NetworkListener listener = new NetworkListener("grizzly", host, port);
+
+ listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(new ThreadFactoryBuilder()
+ .setNameFormat("grizzly-http-server-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ listener.setSecure(secure);
+ if (sslEngineConfigurator != null) {
+ listener.setSSLEngineConfig(sslEngineConfigurator);
+ }
+
+ final HttpServer server = new HttpServer();
+ server.addListener(listener);
+
+ // Map the path to the processor.
+ final ServerConfiguration config = server.getServerConfiguration();
+ if (handler != null) {
+ final String path = uri.getPath().replaceAll("/{2,}", "/");
+
+ final String contextPath = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
+ config.addHttpHandler(handler, HttpHandlerRegistration.bulder().contextPath(contextPath).build());
+ }
+
+ config.setPassTraceRequest(true);
+ config.setDefaultQueryEncoding(Charsets.UTF8_CHARSET);
+
+ if (start) {
+ try {
+ // Start the server.
+ server.start();
+ } catch (final IOException ex) {
+ server.shutdownNow();
+ throw new ProcessingException(LocalizationMessages.FAILED_TO_START_SERVER(ex.getMessage()), ex);
+ }
+ }
+
+ return server;
+ }
+
+ /**
+ * Prevents instantiation.
+ */
+ private GrizzlyHttpServerFactory() {
+ }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java
new file mode 100644
index 0000000..d14917f
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, 2018 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.grizzly2.httpserver;
+
+import java.util.Collection;
+
+import org.glassfish.jersey.internal.PropertiesDelegate;
+
+import org.glassfish.grizzly.http.server.Request;
+
+/**
+ * Grizzly container {@link PropertiesDelegate properties delegate}.
+ *
+ * @author Martin Matula
+ */
+class GrizzlyRequestPropertiesDelegate implements PropertiesDelegate {
+ private final Request request;
+
+ /**
+ * Create new Grizzly container properties delegate instance.
+ *
+ * @param request grizzly HTTP request.
+ */
+ GrizzlyRequestPropertiesDelegate(Request request) {
+ this.request = request;
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return request.getAttribute(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ return request.getAttributeNames();
+ }
+
+ @Override
+ public void setProperty(String name, Object value) {
+ request.setAttribute(name, value);
+ }
+
+ @Override
+ public void removeProperty(String name) {
+ request.removeAttribute(name);
+ }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java
new file mode 100644
index 0000000..75cf389
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 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 Grizzly 2.x container classes.
+ */
+package org.glassfish.jersey.grizzly2.httpserver;
diff --git a/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..57f405d
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainerProvider
\ No newline at end of file
diff --git a/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties b/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties
new file mode 100644
index 0000000..83ce439
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2013, 2018 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
+#
+
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+# {0} - exception message
+failed.to.start.server=Failed to start Grizzly HTTP server: {0}
diff --git a/containers/grizzly2-servlet/pom.xml b/containers/grizzly2-servlet/pom.xml
new file mode 100644
index 0000000..caee668
--- /dev/null
+++ b/containers/grizzly2-servlet/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-grizzly2-servlet</name>
+
+ <description>Grizzly 2 Servlet Container.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>${servlet4.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-http-servlet</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.servlet.*;version="[2.4,5.0)",
+ *
+ </Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
new file mode 100644
index 0000000..db969dd
--- /dev/null
+++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2012, 2018 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.grizzly2.servlet;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.uri.UriComponent;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.servlet.ServletRegistration;
+import org.glassfish.grizzly.servlet.WebappContext;
+
+/**
+ * Factory for creating and starting Grizzly 2 {@link HttpServer} instances
+ * for deploying a {@code Servlet}.
+ * <p/>
+ * The default deployed server is an instance of {@link ServletContainer}.
+ * <p/>
+ * If no initialization parameters are declared (or is null) then root
+ * resource and provider classes will be found by searching the classes
+ * referenced in the java classpath.
+ *
+ * @author Paul Sandoz
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public final class GrizzlyWebContainerFactory {
+
+ private GrizzlyWebContainerFactory() {
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws java.io.IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(String u)
+ throws IOException, IllegalArgumentException {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u));
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(String u, Map<String, String> initParams)
+ throws IOException, IllegalArgumentException {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), initParams);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(URI u)
+ throws IOException, IllegalArgumentException {
+ return create(u, ServletContainer.class);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(URI u,
+ Map<String, String> initParams) throws IOException {
+ return create(u, ServletContainer.class, initParams);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(String u, Class<? extends Servlet> c) throws IOException {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), c);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(String u, Class<? extends Servlet> c,
+ Map<String, String> initParams) throws IOException {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), c, initParams);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(URI u, Class<? extends Servlet> c) throws IOException {
+ return create(u, c, null);
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(URI u, Class<? extends Servlet> c,
+ Map<String, String> initParams) throws IOException {
+ return create(u, c, null, initParams, null);
+ }
+
+ private static HttpServer create(URI u, Class<? extends Servlet> c, Servlet servlet,
+ Map<String, String> initParams, Map<String, String> contextInitParams)
+ throws IOException {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ String path = u.getPath();
+ if (path == null) {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be non-null");
+ } else if (path.isEmpty()) {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be present");
+ } else if (path.charAt(0) != '/') {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ". must start with a '/'");
+ }
+
+ path = String.format("/%s", UriComponent.decodePath(u.getPath(), true).get(1).toString());
+
+ WebappContext context = new WebappContext("GrizzlyContext", path);
+ ServletRegistration registration;
+ if (c != null) {
+ registration = context.addServlet(c.getName(), c);
+ } else {
+ registration = context.addServlet(servlet.getClass().getName(), servlet);
+ }
+ registration.addMapping("/*");
+
+ if (contextInitParams != null) {
+ for (Map.Entry<String, String> e : contextInitParams.entrySet()) {
+ context.setInitParameter(e.getKey(), e.getValue());
+ }
+ }
+
+ if (initParams != null) {
+ registration.setInitParameters(initParams);
+ }
+
+ HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u);
+ context.deploy(server);
+ return server;
+ }
+
+ /**
+ * Create a {@link HttpServer} that registers the declared servlet instance.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param servlet the servlet instance.
+ * @param initParams the servlet initialization parameters.
+ * @param contextInitParams the servlet context initialization parameters.
+ * @return the http server, with the endpoint started.
+ *
+ * @throws IOException if an error occurs creating the container.
+ * @throws IllegalArgumentException if {@code u} is {@code null}.
+ */
+ public static HttpServer create(URI u, Servlet servlet, Map<String, String> initParams, Map<String, String> contextInitParams)
+ throws IOException {
+ if (servlet == null) {
+ throw new IllegalArgumentException("The servlet must not be null");
+ }
+ return create(u, null, servlet, initParams, contextInitParams);
+ }
+}
diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java
new file mode 100644
index 0000000..41a7e50
--- /dev/null
+++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 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 Grizzly 2.x Servlet container classes.
+ */
+package org.glassfish.jersey.grizzly2.servlet;
diff --git a/containers/jdk-http/pom.xml b/containers/jdk-http/pom.xml
new file mode 100644
index 0000000..41c87bd
--- /dev/null
+++ b/containers/jdk-http/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-jdk-http</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-jdk-http</name>
+
+ <description>JDK Http Container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ </plugin>
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>windows</id>
+ <activation>
+ <os>
+ <family>windows</family>
+ </os>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- Exclude unit tests regarding JDK HTTP Server because of failing a server shutdown in a class
+ with several tests. "java.net.BindException: Address already in use: bind"
+ bug reported on https://bugs.openjdk.java.net/browse/JDK-8015692 -->
+ <excludes>
+ <exclude>org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java</exclude>
+ <exclude>org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java</exclude>
+ <exclude>org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java</exclude>
+ <exclude>org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java
new file mode 100644
index 0000000..051c5ca
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2012, 2018 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.jdkhttp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
+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.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsExchange;
+
+/**
+ * Jersey {@code Container} implementation based on Java SE {@link HttpServer}.
+ *
+ * @author Miroslav Fuksa
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JdkHttpHandlerContainer implements HttpHandler, Container {
+
+ private static final Logger LOGGER = Logger.getLogger(JdkHttpHandlerContainer.class.getName());
+
+ private volatile ApplicationHandler appHandler;
+
+ /**
+ * Create new lightweight Java SE HTTP server container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on the container.
+ */
+ JdkHttpHandlerContainer(final Application application) {
+ this.appHandler = new ApplicationHandler(application);
+ }
+
+ /**
+ * Create new lightweight Java SE HTTP server container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on the container.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ */
+ JdkHttpHandlerContainer(final Application application, final Object parentContext) {
+ this.appHandler = new ApplicationHandler(application, null, parentContext);
+ }
+
+ @Override
+ public void handle(final HttpExchange exchange) throws IOException {
+ /**
+ * This is a URI that contains the path, query and fragment components.
+ */
+ URI exchangeUri = exchange.getRequestURI();
+
+ /**
+ * The base path specified by the HTTP context of the HTTP handler. It
+ * is in decoded form.
+ */
+ String decodedBasePath = exchange.getHttpContext().getPath();
+
+ // Ensure that the base path ends with a '/'
+ if (!decodedBasePath.endsWith("/")) {
+ if (decodedBasePath.equals(exchangeUri.getPath())) {
+ /**
+ * This is an edge case where the request path does not end in a
+ * '/' and is equal to the context path of the HTTP handler.
+ * Both the request path and base path need to end in a '/'
+ * Currently the request path is modified.
+ *
+ * TODO support redirection in accordance with resource configuration feature.
+ */
+ exchangeUri = UriBuilder.fromUri(exchangeUri)
+ .path("/").build();
+ }
+ decodedBasePath += "/";
+ }
+
+ /*
+ * The following is madness, there is no easy way to get the complete
+ * URI of the HTTP request!!
+ *
+ * TODO this is missing the user information component, how can this be obtained?
+ */
+ final boolean isSecure = exchange instanceof HttpsExchange;
+ final String scheme = isSecure ? "https" : "http";
+
+ final URI baseUri = getBaseUri(exchange, decodedBasePath, scheme);
+ final URI requestUri = getRequestUri(exchange, baseUri);
+
+ final ResponseWriter responseWriter = new ResponseWriter(exchange);
+ final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri,
+ exchange.getRequestMethod(), getSecurityContext(exchange.getPrincipal(), isSecure),
+ new MapPropertiesDelegate());
+ requestContext.setEntityStream(exchange.getRequestBody());
+ requestContext.getHeaders().putAll(exchange.getRequestHeaders());
+ requestContext.setWriter(responseWriter);
+ try {
+ appHandler.handle(requestContext);
+ } finally {
+ // if the response was not committed yet by the JerseyApplication
+ // then commit it and log warning
+ responseWriter.closeAndLogWarning();
+ }
+ }
+
+ private URI getBaseUri(final HttpExchange exchange, final String decodedBasePath, final String scheme) {
+ final URI baseUri;
+ try {
+ final List<String> hostHeader = exchange.getRequestHeaders().get("Host");
+ if (hostHeader != null) {
+ baseUri = new URI(scheme + "://" + hostHeader.get(0) + decodedBasePath);
+ } else {
+ final InetSocketAddress addr = exchange.getLocalAddress();
+ baseUri = new URI(scheme, null, addr.getHostName(), addr.getPort(),
+ decodedBasePath, null, null);
+ }
+ } catch (final URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ return baseUri;
+ }
+
+ private URI getRequestUri(final HttpExchange exchange, final URI baseUri) {
+ try {
+ return new URI(getServerAddress(baseUri) + exchange.getRequestURI());
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getServerAddress(final URI baseUri) throws URISyntaxException {
+ return new URI(baseUri.getScheme(), null, baseUri.getHost(), baseUri.getPort(), null, null, null).toString();
+ }
+
+ private SecurityContext getSecurityContext(final Principal principal, final boolean isSecure) {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return isSecure;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration);
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ /**
+ * Inform this container that the server has been started.
+ *
+ * This method must be implicitly called after the server containing this container is started.
+ */
+ void onServerStart() {
+ this.appHandler.onStartup(this);
+ }
+
+ /**
+ * Inform this container that the server is being stopped.
+ *
+ * This method must be implicitly called before the server containing this container is stopped.
+ */
+ void onServerStop() {
+ this.appHandler.onShutdown(this);
+ }
+
+ private static final class ResponseWriter implements ContainerResponseWriter {
+
+ private final HttpExchange exchange;
+ private final AtomicBoolean closed;
+
+ /**
+ * Creates a new ResponseWriter for given {@link HttpExchange HTTP Exchange}.
+ *
+ * @param exchange Exchange of the {@link HttpServer JDK Http Server}
+ */
+ ResponseWriter(final HttpExchange exchange) {
+ this.exchange = exchange;
+ this.closed = new AtomicBoolean(false);
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse context)
+ throws ContainerException {
+ final MultivaluedMap<String, String> responseHeaders = context.getStringHeaders();
+ final Headers serverHeaders = exchange.getResponseHeaders();
+ for (final Map.Entry<String, List<String>> e : responseHeaders.entrySet()) {
+ for (final String value : e.getValue()) {
+ serverHeaders.add(e.getKey(), value);
+ }
+ }
+
+ try {
+ if (context.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
+ // Work around bug in LW HTTP server
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6886436
+ exchange.sendResponseHeaders(context.getStatus(), -1);
+ } else {
+ exchange.sendResponseHeaders(context.getStatus(),
+ getResponseLength(contentLength));
+ }
+ } catch (final IOException ioe) {
+ throw new ContainerException(LocalizationMessages.ERROR_RESPONSEWRITER_WRITING_HEADERS(), ioe);
+ }
+
+ return exchange.getResponseBody();
+ }
+
+ private long getResponseLength(final long contentLength) {
+ if (contentLength == 0) {
+ return -1;
+ }
+ if (contentLength < 0) {
+ return 0;
+ }
+ return contentLength;
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+ throw new UnsupportedOperationException("Method suspend is not supported by the container.");
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+ throw new UnsupportedOperationException("Method setSuspendTimeout is not supported by the container.");
+ }
+
+ @Override
+ public void failure(final Throwable error) {
+ try {
+ exchange.sendResponseHeaders(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), getResponseLength(0));
+ } catch (final IOException e) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_RESPONSEWRITER_SENDING_FAILURE_RESPONSE(), e);
+ } finally {
+ commit();
+ rethrow(error);
+ }
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true;
+ }
+
+ @Override
+ public void commit() {
+ if (closed.compareAndSet(false, true)) {
+ exchange.close();
+ }
+ }
+
+ /**
+ * Rethrow the original exception as required by JAX-RS, 3.3.4
+ *
+ * @param error throwable to be re-thrown
+ */
+ private void rethrow(final Throwable error) {
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+
+ /**
+ * Commits the response and logs a warning message.
+ *
+ * This method should be called by the container at the end of the
+ * handle method to make sure that the ResponseWriter was committed.
+ */
+ private void closeAndLogWarning() {
+ if (closed.compareAndSet(false, true)) {
+ exchange.close();
+ LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_RESPONSEWRITER_RESPONSE_UNCOMMITED());
+ }
+ }
+ }
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java
new file mode 100644
index 0000000..cd6306d
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, 2018 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.jdkhttp;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Container provider for containers based on lightweight Java SE HTTP Server's {@link HttpHandler}.
+ *
+ * @author Miroslav Fuksa
+ */
+public final class JdkHttpHandlerContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+ if (type != HttpHandler.class && type != JdkHttpHandlerContainer.class) {
+ return null;
+ }
+ return type.cast(new JdkHttpHandlerContainer(application));
+ }
+}
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
new file mode 100644
index 0000000..0f1fbb2
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2010, 2018 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.jdkhttp;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ProcessingException;
+
+import javax.net.ssl.SSLContext;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+/**
+ * Factory for creating {@link HttpServer JDK HttpServer} instances to run Jersey applications.
+ *
+ * @author Miroslav Fuksa
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JdkHttpServerFactory {
+
+ private static final Logger LOG = Logger.getLogger(JdkHttpServerFactory.class.getName());
+
+ /**
+ * Create and start the {@link HttpServer JDK HttpServer} with the Jersey application deployed
+ * at the given {@link URI}.
+ * <p>
+ * The returned {@link HttpServer JDK HttpServer} is started.
+ * </p>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation
+ * occurs.
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration) {
+ return createHttpServer(uri, configuration, true);
+ }
+
+ /**
+ * Create (and possibly start) the {@link HttpServer JDK HttpServer} with the JAX-RS / Jersey application deployed
+ * on the given {@link URI}.
+ * <p>
+ * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+ * </p>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @param start if set to {@code false}, the created server will not be automatically started.
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation occurs.
+ * @since 2.8
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration, final boolean start) {
+ return createHttpServer(uri, new JdkHttpHandlerContainer(configuration), start);
+ }
+
+ /**
+ * Create (and possibly start) the {@link HttpServer JDK HttpServer} with the JAX-RS / Jersey application deployed
+ * on the given {@link URI}.
+ * <p/>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation occurs.
+ * @see org.glassfish.jersey.jdkhttp.JdkHttpHandlerContainer
+ * @since 2.12
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+ final Object parentContext) {
+ return createHttpServer(uri, new JdkHttpHandlerContainer(configuration, parentContext), true);
+ }
+
+ /**
+ * Create and start the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+ * {@link HttpsServer JDK HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+ * <p>
+ * The returned {@link HttpServer JDK HttpServer} is started.
+ * </p>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @param sslContext custom {@link SSLContext} to be passed to the server
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation occurs.
+ * @since 2.18
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+ final SSLContext sslContext) {
+ return createHttpServer(uri, new JdkHttpHandlerContainer(configuration),
+ sslContext, true);
+ }
+
+ /**
+ * Create (and possibly start) the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+ * {@link HttpsServer JDK HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+ * <p>
+ * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+ * </p>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @param sslContext custom {@link SSLContext} to be passed to the server
+ * @param start if set to {@code false}, the created server will not be automatically started.
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation occurs.
+ * @since 2.17
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+ final SSLContext sslContext, final boolean start) {
+ return createHttpServer(uri,
+ new JdkHttpHandlerContainer(configuration),
+ sslContext,
+ start);
+ }
+
+ /**
+ * Create (and possibly start) the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+ * {@link HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+ * <p>
+ * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+ * </p>
+ *
+ * @param uri the {@link URI uri} on which the Jersey application will be deployed.
+ * @param configuration the Jersey server-side application configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @param sslContext custom {@link SSLContext} to be passed to the server
+ * @param start if set to {@code false}, the created server will not be automatically started.
+ * @return Newly created {@link HttpServer}.
+ * @throws ProcessingException thrown when problems during server creation occurs.
+ * @since 2.18
+ */
+ public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+ final Object parentContext,
+ final SSLContext sslContext, final boolean start) {
+ return createHttpServer(uri,
+ new JdkHttpHandlerContainer(configuration, parentContext),
+ sslContext,
+ start
+ );
+ }
+
+ private static HttpServer createHttpServer(final URI uri, final JdkHttpHandlerContainer handler,
+ final boolean start) {
+ return createHttpServer(uri, handler, null, start);
+ }
+
+ private static HttpServer createHttpServer(final URI uri,
+ final JdkHttpHandlerContainer handler,
+ final SSLContext sslContext,
+ final boolean start) {
+ if (uri == null) {
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_NULL());
+ }
+
+ 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;
+
+ if (isHttp) {
+ if (httpsConfigurator != null) {
+ // attempt to use https with http scheme
+ LOG.warning(LocalizationMessages.WARNING_CONTAINER_URI_SCHEME_SECURED());
+ }
+ } else if (isHttps) {
+ if (httpsConfigurator == null) {
+ if (start) {
+ // The SSLContext (via HttpsConfigurator) has to be set before the server starts.
+ // Starting https server w/o SSL is invalid, it will lead to error anyway.
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_HTTPS_NO_SSL());
+ } else {
+ // Creating the https server w/o SSL context, but not starting it is valid.
+ // However, server.setHttpsConfigurator() must be called before the start.
+ LOG.info(LocalizationMessages.INFO_CONTAINER_HTTPS_NO_SSL());
+ }
+ }
+ } else {
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_SCHEME_UNKNOWN(uri));
+ }
+
+ final String path = uri.getPath();
+ if (path == null) {
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_NULL(uri));
+ } else if (path.isEmpty()) {
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_EMPTY(uri));
+ } else if (path.charAt(0) != '/') {
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_START(uri));
+ }
+
+ final int port = (uri.getPort() == -1)
+ ? (isHttp ? Container.DEFAULT_HTTP_PORT : Container.DEFAULT_HTTPS_PORT)
+ : uri.getPort();
+
+ final HttpServer server;
+ try {
+ server = isHttp
+ ? HttpServer.create(new InetSocketAddress(port), 0)
+ : HttpsServer.create(new InetSocketAddress(port), 0);
+ } catch (final IOException ioe) {
+ throw new ProcessingException(LocalizationMessages.ERROR_CONTAINER_EXCEPTION_IO(), ioe);
+ }
+
+ if (isHttps && httpsConfigurator != null) {
+ ((HttpsServer) server).setHttpsConfigurator(httpsConfigurator);
+ }
+
+ server.setExecutor(Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("jdk-http-server-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build()));
+ server.createContext(path, handler);
+
+ final HttpServer wrapper = isHttp
+ ? createHttpServerWrapper(server, handler)
+ : createHttpsServerWrapper((HttpsServer) server, handler);
+
+ if (start) {
+ wrapper.start();
+ }
+
+ return wrapper;
+ }
+
+ private static HttpServer createHttpsServerWrapper(final HttpsServer delegate, final JdkHttpHandlerContainer handler) {
+ return new HttpsServer() {
+
+ @Override
+ public void setHttpsConfigurator(final HttpsConfigurator httpsConfigurator) {
+ delegate.setHttpsConfigurator(httpsConfigurator);
+ }
+
+ @Override
+ public HttpsConfigurator getHttpsConfigurator() {
+ return delegate.getHttpsConfigurator();
+ }
+
+ @Override
+ public void bind(final InetSocketAddress inetSocketAddress, final int i) throws IOException {
+ delegate.bind(inetSocketAddress, i);
+ }
+
+ @Override
+ public void start() {
+ delegate.start();
+ handler.onServerStart();
+ }
+
+ @Override
+ public void setExecutor(final Executor executor) {
+ delegate.setExecutor(executor);
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return delegate.getExecutor();
+ }
+
+ @Override
+ public void stop(final int i) {
+ handler.onServerStop();
+ delegate.stop(i);
+ }
+
+ @Override
+ public HttpContext createContext(final String s, final HttpHandler httpHandler) {
+ return delegate.createContext(s, httpHandler);
+ }
+
+ @Override
+ public HttpContext createContext(final String s) {
+ return delegate.createContext(s);
+ }
+
+ @Override
+ public void removeContext(final String s) throws IllegalArgumentException {
+ delegate.removeContext(s);
+ }
+
+ @Override
+ public void removeContext(final HttpContext httpContext) {
+ delegate.removeContext(httpContext);
+ }
+
+ @Override
+ public InetSocketAddress getAddress() {
+ return delegate.getAddress();
+ }
+ };
+ }
+
+ private static HttpServer createHttpServerWrapper(final HttpServer delegate, final JdkHttpHandlerContainer handler) {
+ return new HttpServer() {
+
+ @Override
+ public void bind(final InetSocketAddress inetSocketAddress, final int i) throws IOException {
+ delegate.bind(inetSocketAddress, i);
+ }
+
+ @Override
+ public void start() {
+ delegate.start();
+ handler.onServerStart();
+ }
+
+ @Override
+ public void setExecutor(final Executor executor) {
+ delegate.setExecutor(executor);
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return delegate.getExecutor();
+ }
+
+ @Override
+ public void stop(final int i) {
+ handler.onServerStop();
+ delegate.stop(i);
+ }
+
+ @Override
+ public HttpContext createContext(final String s, final HttpHandler httpHandler) {
+ return delegate.createContext(s, httpHandler);
+ }
+
+ @Override
+ public HttpContext createContext(final String s) {
+ return delegate.createContext(s);
+ }
+
+ @Override
+ public void removeContext(final String s) throws IllegalArgumentException {
+ delegate.removeContext(s);
+ }
+
+ @Override
+ public void removeContext(final HttpContext httpContext) {
+ delegate.removeContext(httpContext);
+ }
+
+ @Override
+ public InetSocketAddress getAddress() {
+ return delegate.getAddress();
+ }
+ };
+ }
+
+ /**
+ * Prevents instantiation.
+ */
+ private JdkHttpServerFactory() {
+ throw new AssertionError("Instantiation not allowed.");
+ }
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java
new file mode 100644
index 0000000..c60ec6e
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010, 2018 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
+ */
+
+/**
+ * The container adapter between {@link com.sun.net.httpserver.HttpServer JDK HTTP server}
+ * and Jersey {@link org.glassfish.jersey.server.ApplicationHandler Jersey application handler}
+ * classes.
+ */
+package org.glassfish.jersey.jdkhttp;
diff --git a/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..efdf94e
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jdkhttp.JdkHttpHandlerContainerProvider
\ No newline at end of file
diff --git a/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties b/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties
new file mode 100644
index 0000000..745c494
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2010, 2018 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
+#
+
+error.container.exception.io=IOException thrown when creating the JDK HttpServer.
+error.container.uri.null=The URI must not be null.
+error.container.uri.path.empty=The URI path, of the URI {0} must be present (not an empty string).
+error.container.uri.path.null=The URI path, of the URI {0} must be non-null.
+error.container.uri.path.start=The URI path, of the URI {0} must start with a '/'.
+error.container.uri.scheme.unknown=The URI scheme, of the URI {0} must be equal (ignoring case) to 'http' or 'https'.
+error.container.https.no.ssl=Attempt to start a HTTPS server with no SSL context defined.
+error.responsewriter.response.uncommited=ResponseWriter was not commited yet. Committing the Response now.
+error.responsewriter.sending.failure.response=Unable to send a failure response.
+error.responsewriter.writing.headers=Error writing out the response headers.
+info.container.https.no.ssl=HTTPS server will be created with no SSL context defined. HttpsConfigurator must be \
+ set before the server is started.
+warning.container.uri.scheme.secured=SSLContext is set, but http scheme was used instead of https. The SSLContext will \
+ be ignored.
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
new file mode 100644
index 0000000..24998f9
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2013, 2018 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.jdkhttp;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import com.sun.net.httpserver.HttpServer;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.After;
+
+/**
+ * Abstract JDK HTTP Server unit tester.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public abstract class AbstractJdkHttpServerTester {
+
+ public static final String CONTEXT = "";
+ private final int DEFAULT_PORT = 9998;
+
+ private static final Logger LOGGER = Logger.getLogger(AbstractJdkHttpServerTester.class.getName());
+
+ /**
+ * Get the port to be used for test application deployments.
+ *
+ * @return The HTTP port of the URI
+ */
+ protected 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 not positive.");
+ }
+ return i;
+ } catch (NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid positive integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+ return DEFAULT_PORT;
+ }
+
+ private volatile HttpServer server;
+
+ public UriBuilder getUri() {
+ return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+ }
+
+ public void startServer(Class... resources) {
+ ResourceConfig config = new ResourceConfig(resources);
+ 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);
+ }
+
+ 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);
+ }
+
+ public URI getBaseUri() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+ }
+
+ public void stopServer() {
+ try {
+ server.stop(3);
+ server = null;
+ LOGGER.log(Level.INFO, "Simple-http server stopped.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ stopServer();
+ }
+ }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java
new file mode 100644
index 0000000..62a344d
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 2018 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.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.After;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsServer;
+
+/**
+ * Jdk Http Server basic tests.
+ *
+ * @author Michal Gajdos
+ */
+public class BasicJdkHttpServerTest extends AbstractJdkHttpServerTester {
+
+ private HttpServer server;
+
+ @Path("/test")
+ public static class TestResource {
+
+ @GET
+ public String get() {
+ return "test";
+ }
+ }
+
+ @Test
+ public void testCreateHttpServer() throws Exception {
+ server = JdkHttpServerFactory.createHttpServer(
+ UriBuilder.fromUri("http://localhost/").port(getPort()).build(), new ResourceConfig(TestResource.class));
+
+ assertThat(server, instanceOf(HttpServer.class));
+ assertThat(server, not(instanceOf(HttpsServer.class)));
+ }
+
+ @Test
+ public void testCreateHttpsServer() throws Exception {
+ server = JdkHttpServerFactory.createHttpServer(
+ UriBuilder.fromUri("https://localhost/").port(getPort()).build(),
+ new ResourceConfig(TestResource.class),
+ false);
+
+ assertThat(server, instanceOf(HttpsServer.class));
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ server.stop(3);
+ server = null;
+ }
+ }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java
new file mode 100644
index 0000000..675bbaa
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013, 2018 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.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jdk Http Container package scanning test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JdkHttpPackageTest extends AbstractJdkHttpServerTester {
+
+ @Path("/packageTest")
+ public static class TestResource {
+ @GET
+ public String get() {
+ return "test";
+ }
+
+ @GET
+ @Path("sub")
+ public String getSub() {
+ return "test-sub";
+ }
+ }
+
+ @Test
+ public void testJdkHttpPackage() {
+ final ResourceConfig rc = new ResourceConfig();
+ rc.packages(this.getClass().getPackage().getName());
+
+ startServer(rc);
+
+ WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ assertEquals("test", r.path("packageTest").request().get(String.class));
+ assertEquals("test-sub", r.path("packageTest/sub").request().get(String.class));
+ assertEquals(404, r.path("wrong").request().get(Response.class).getStatus());
+
+ }
+}
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
new file mode 100644
index 0000000..180f2bc
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2015, 2018 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.jdkhttp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.After;
+import org.junit.Test;
+
+import com.google.common.io.ByteStreams;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jdk Https Server tests.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class JdkHttpsServerTest extends AbstractJdkHttpServerTester {
+
+ private static final String TRUSTSTORE_CLIENT_FILE = "./truststore_client";
+ private static final String TRUSTSTORE_CLIENT_PWD = "asdfgh";
+ private static final String KEYSTORE_CLIENT_FILE = "./keystore_client";
+ private static final String KEYSTORE_CLIENT_PWD = "asdfgh";
+
+ private static final String KEYSTORE_SERVER_FILE = "./keystore_server";
+ private static final String KEYSTORE_SERVER_PWD = "asdfgh";
+ 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")
+ public static class TestResource {
+ @GET
+ public String get() {
+ return "test";
+ }
+ }
+
+ /**
+ * Test, that {@link HttpsServer} instance is returned when providing empty SSLContext (but not starting).
+ * @throws Exception
+ */
+ @Test
+ public void testCreateHttpsServerNoSslContext() throws Exception {
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+ assertThat(server, instanceOf(HttpsServer.class));
+ }
+
+ /**
+ * Test, that exception is thrown when attempting to start a {@link HttpsServer} with empty SSLContext.
+ * @throws Exception
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testStartHttpServerNoSslContext() throws Exception {
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, true);
+ }
+
+ /**
+ * Test, that {@link javax.net.ssl.SSLHandshakeException} is thrown when attepmting to connect to server with client
+ * not configured correctly.
+ * @throws Exception
+ */
+ @Test(expected = SSLHandshakeException.class)
+ public void testCreateHttpsServerDefaultSslContext() throws Throwable {
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+ } catch (final ProcessingException e) {
+ throw e.getCause();
+ }
+ }
+
+ /**
+ * Test, that {@link HttpsServer} can be manually started even with (empty) SSLContext, but will throw an exception
+ * on request.
+ * @throws Exception
+ */
+ @Test(expected = IOException.class)
+ public void testHttpsServerNoSslContextDelayedStart() throws Throwable {
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+ } catch (final ProcessingException e) {
+ throw e.getCause();
+ }
+ }
+
+ /**
+ * Test, that {@link HttpsServer} cannot be configured with {@link HttpsConfigurator} after it has started.
+ * @throws Exception
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testConfigureSslContextAfterStart() throws Throwable {
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+ assertThat(server, instanceOf(HttpsServer.class));
+ server.start();
+ ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(getServerSslContext()));
+ }
+
+ /**
+ * Tests a client to server roundtrip with correctly configured SSL on both sides.
+ * @throws IOException
+ */
+ @Test
+ public void testCreateHttpsServerRoundTrip() throws IOException {
+ final SSLContext serverSslContext = getServerSslContext();
+
+ server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+
+ assertEquals("test", response);
+ }
+
+ /**
+ * Test, that if URI uses http scheme instead of https, SSLContext is ignored.
+ * @throws IOException
+ */
+ @Test
+ public void testHttpWithSsl() throws IOException {
+ server = JdkHttpServerFactory.createHttpServer(httpUri, rc, getServerSslContext(), true);
+ assertThat(server, instanceOf(HttpServer.class));
+ assertThat(server, not(instanceOf(HttpsServer.class)));
+ }
+
+ private SSLContext getClientSslContext() throws IOException {
+ final InputStream trustStore = JdkHttpsServerTest.class.getResourceAsStream(TRUSTSTORE_CLIENT_FILE);
+ final InputStream keyStore = JdkHttpsServerTest.class.getResourceAsStream(KEYSTORE_CLIENT_FILE);
+
+
+ final SslConfigurator sslConfigClient = SslConfigurator.newInstance()
+ .trustStoreBytes(ByteStreams.toByteArray(trustStore))
+ .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
+ .keyStoreBytes(ByteStreams.toByteArray(keyStore))
+ .keyPassword(KEYSTORE_CLIENT_PWD);
+
+ return sslConfigClient.createSSLContext();
+ }
+
+ private SSLContext getServerSslContext() throws IOException {
+ final InputStream trustStore = JdkHttpsServerTest.class.getResourceAsStream(TRUSTSTORE_SERVER_FILE);
+ final InputStream keyStore = JdkHttpsServerTest.class.getResourceAsStream(KEYSTORE_SERVER_FILE);
+
+ final SslConfigurator sslConfigServer = SslConfigurator.newInstance()
+ .keyStoreBytes(ByteStreams.toByteArray(keyStore))
+ .keyPassword(KEYSTORE_SERVER_PWD)
+ .trustStoreBytes(ByteStreams.toByteArray(trustStore))
+ .trustStorePassword(TRUSTSTORE_SERVER_PWD);
+
+ return sslConfigServer.createSSLContext();
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ server.stop(0);
+ server = null;
+ }
+ }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java
new file mode 100644
index 0000000..229058f
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, 2018 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.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractJdkHttpServerTester {
+
+ @Path("/one")
+ public static class One {
+ @GET
+ public String get() {
+ return "one";
+ }
+
+ @GET
+ @Path("sub")
+ public String getSub() {
+ return "one-sub";
+ }
+ }
+
+ @Path("/two")
+ public static class Two {
+ @GET
+ public String get() {
+ return "two";
+ }
+ }
+
+ public static class Reloader extends AbstractContainerLifecycleListener {
+ Container container;
+
+ public void reload(ResourceConfig newConfig) {
+ container.reload(newConfig);
+ }
+
+ public void reload() {
+ container.reload();
+ }
+
+ @Override
+ public void onStartup(Container container) {
+ this.container = container;
+ }
+ }
+
+ @Test
+ public void testReload() {
+ final ResourceConfig rc = new ResourceConfig(One.class);
+
+ Reloader reloader = new Reloader();
+ rc.registerInstances(reloader);
+
+ startServer(rc);
+
+ WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals("one-sub", r.path("one/sub").request().get(String.class));
+ assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+ // add Two resource
+ reloader.reload(new ResourceConfig(One.class, Two.class));
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals("one-sub", r.path("one/sub").request().get(String.class));
+ assertEquals("two", r.path("two").request().get(String.class));
+ }
+
+ static class StartStopListener extends AbstractContainerLifecycleListener {
+ volatile boolean started;
+ volatile boolean stopped;
+
+ @Override
+ public void onStartup(Container container) {
+ started = true;
+ }
+
+ @Override
+ public void onShutdown(Container container) {
+ stopped = true;
+ }
+ }
+
+ @Test
+ public void testStartupShutdownHooks() {
+ final StartStopListener listener = new StartStopListener();
+
+ startServer(new ResourceConfig(One.class).register(listener));
+
+ WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ assertThat(r.path("one").request().get(String.class), equalTo("one"));
+ assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+ stopServer();
+
+ assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+ assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+ }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java
new file mode 100644
index 0000000..26abd46
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2014, 2018 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.jdkhttp;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.RuntimeDelegate;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * @author Michal Gajdos
+ */
+public class RuntimeDelegateTest {
+
+ @Path("/")
+ public static class Resource {
+
+ @GET
+ public String get() {
+ return "get";
+ }
+ }
+
+ @Test
+ public void testFetch() throws Exception {
+ final HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
+ final HttpHandler handler = RuntimeDelegate.getInstance().createEndpoint(new Application() {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return Collections.<Class<?>>singleton(Resource.class);
+ }
+ }, HttpHandler.class);
+
+ try {
+ server.createContext("/", handler);
+ server.start();
+
+ final Response response = ClientBuilder.newClient()
+ .target(UriBuilder.fromUri("http://localhost/").port(server.getAddress().getPort()).build())
+ .request()
+ .get();
+
+ assertThat(response.readEntity(String.class), is("get"));
+ } finally {
+ server.stop(0);
+ }
+ }
+}
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client
new file mode 100644
index 0000000..d016fd2
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server
new file mode 100644
index 0000000..a7c93fc
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client
new file mode 100644
index 0000000..74784fb
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server
new file mode 100644
index 0000000..9b26ce4
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server
Binary files differ
diff --git a/containers/jersey-servlet-core/pom.xml b/containers/jersey-servlet-core/pom.xml
new file mode 100644
index 0000000..4bc5700
--- /dev/null
+++ b/containers/jersey-servlet-core/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-servlet-core</name>
+
+ <description>Jersey core Servlet 2.x implementation</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- Note: When you're changing these properties change them also in bundles/jax-rs-ri/bundle/pom.xml. -->
+ <Import-Package>
+ javax.persistence.*;resolution:=optional,
+ javax.servlet.*;version="[2.4,5.0)",
+ javax.annotation.*;version=!,
+ *
+ </Import-Package>
+ <Export-Package>org.glassfish.jersey.servlet.*</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java
new file mode 100644
index 0000000..f54fc00
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriBuilderException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
+import org.glassfish.jersey.servlet.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.ResponseWriter;
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+import org.glassfish.jersey.uri.UriComponent;
+
+/**
+ * A {@link javax.servlet.Servlet} or {@link Filter} for deploying root resource classes.
+ * <p />
+ * The following sections make reference to initialization parameters. Unless
+ * otherwise specified the initialization parameters apply to both server
+ * and filter initialization parameters.
+ * <p />
+ * The servlet or filter may be configured to have an initialization
+ * parameter {@value ServletProperties#JAXRS_APPLICATION_CLASS}
+ * (see {@link org.glassfish.jersey.servlet.ServletProperties#JAXRS_APPLICATION_CLASS}) and whose value is a
+ * fully qualified name of a class that implements {@link javax.ws.rs.core.Application}.
+ * The class is instantiated as a singleton component
+ * managed by the runtime, and injection may be performed (the artifacts that
+ * may be injected are limited to injectable providers registered when
+ * the servlet or filter is configured).
+ * <p />
+ * If the initialization parameter {@value ServletProperties#JAXRS_APPLICATION_CLASS}
+ * is not present and a initialization parameter {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_PACKAGES}
+ * is present (see {@link ServerProperties#PROVIDER_PACKAGES}) a new instance of
+ * {@link ResourceConfig} with this configuration is created. The initialization parameter
+ * {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_PACKAGES} MUST be set to provide one or
+ * more package names. Each package name MUST be separated by ';'.
+ * <p />
+ * If none of the above resource configuration related initialization parameters
+ * are present a new instance of {@link ResourceConfig} with {@link WebAppResourcesScanner}
+ * is created. The initialization parameter {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_CLASSPATH}
+ * is present (see {@link ServerProperties#PROVIDER_CLASSPATH}) MAY be
+ * set to provide one or more resource paths. Each path MUST be separated by ';'.
+ * If the initialization parameter is not present then the following resource
+ * paths are utilized: {@code "/WEB-INF/lib"} and {@code "/WEB-INF/classes"}.
+ * <p />
+ * All initialization parameters are added as properties of the created
+ * {@link ResourceConfig}.
+ * <p />
+ * A new {@link org.glassfish.jersey.server.ApplicationHandler} instance will be created and configured such
+ * that the following classes may be injected onto a root resource, provider
+ * and {@link javax.ws.rs.core.Application} classes using {@link javax.ws.rs.core.Context
+ * @Context} annotation:
+ * {@link HttpServletRequest}, {@link HttpServletResponse},
+ * {@link ServletContext}, {@link javax.servlet.ServletConfig} and {@link WebConfig}.
+ * If this class is used as a Servlet then the {@link javax.servlet.ServletConfig} class may
+ * be injected. If this class is used as a servlet filter then the {@link FilterConfig}
+ * class may be injected. {@link WebConfig} may be injected to abstract
+ * servlet or filter deployment.
+ * <p />
+ * Persistence units that may be injected must be configured in web.xml
+ * in the normal way plus an additional servlet parameter to enable the
+ * Jersey servlet to locate them in JNDI. E.g. with the following
+ * persistence unit configuration:
+ * <pre>{@code
+ * <persistence-unit-ref>
+ * <persistence-unit-ref-name>persistence/widget</persistence-unit-ref-name>
+ * <persistence-unit-name>WidgetPU</persistence-unit-name>
+ * </persistence-unit-ref>
+ * }</pre>
+ * the Jersey servlet requires an additional servlet parameter as
+ * follows:
+ * <pre>{@code
+ * <init-param>
+ * <param-name>unit:WidgetPU</param-name>
+ * <param-value>persistence/widget</param-value>
+ * </init-param>
+ * }</pre>
+ * Given the above, Jersey will inject the {@link javax.persistence.EntityManagerFactory EntityManagerFactory} found
+ * at {@code java:comp/env/persistence/widget} in JNDI when encountering a
+ * field or parameter annotated with {@code @PersistenceUnit(unitName="WidgetPU")}.
+ *
+ * @author Paul Sandoz
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class ServletContainer extends HttpServlet implements Filter, Container {
+
+ private static final long serialVersionUID = 3932047066686065219L;
+ private static final ExtendedLogger LOGGER =
+ new ExtendedLogger(Logger.getLogger(ServletContainer.class.getName()), Level.FINEST);
+
+ private transient FilterConfig filterConfig;
+ private transient WebComponent webComponent;
+ private transient ResourceConfig resourceConfig;
+ private transient Pattern staticContentPattern;
+ private transient String filterContextPath;
+ private transient List<String> filterUrlMappings;
+
+ private transient volatile ContainerLifecycleListener containerListener;
+
+ /**
+ * Initiate the Web component.
+ *
+ * @param webConfig the Web configuration.
+ * @throws javax.servlet.ServletException in case of an initialization failure
+ */
+ protected void init(final WebConfig webConfig) throws ServletException {
+ webComponent = new WebComponent(webConfig, resourceConfig);
+ containerListener = webComponent.appHandler;
+ containerListener.onStartup(this);
+ }
+
+ /**
+ * Create Jersey Servlet container.
+ */
+ public ServletContainer() {
+ }
+
+ /**
+ * Create Jersey Servlet container.
+ *
+ * @param resourceConfig container configuration.
+ */
+ public ServletContainer(final ResourceConfig resourceConfig) {
+ this.resourceConfig = resourceConfig;
+ }
+
+ /**
+ * Dispatches client requests to the protected
+ * {@code service} method. There's no need to
+ * override this method.
+ *
+ * @param req the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
+ * @param res the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
+ * @throws IOException if an input or output error occurs
+ * while the servlet is handling the
+ * HTTP request
+ * @throws ServletException if the HTTP request cannot
+ * be handled
+ * @see javax.servlet.Servlet#service
+ */
+ @Override
+ public void service(final ServletRequest req, final ServletResponse res)
+ throws ServletException, IOException {
+ final HttpServletRequest request;
+ final HttpServletResponse response;
+
+ if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
+ throw new ServletException("non-HTTP request or response");
+ }
+
+ request = (HttpServletRequest) req;
+ response = (HttpServletResponse) res;
+
+ service(request, response);
+ }
+
+ /**
+ * Receives standard HTTP requests from the public {@code service} method and dispatches
+ * them to the {@code do}<i>XXX</i> methods defined in
+ * this class. This method is an HTTP-specific version of the
+ * {@link javax.servlet.Servlet#service} method. There's no
+ * need to override this method.
+ *
+ * @param request the {@link HttpServletRequest} object that
+ * contains the request the client made of
+ * the servlet
+ * @param response the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client
+ * @throws IOException if an input or output error occurs
+ * while the servlet is handling the
+ * HTTP request
+ * @throws ServletException if the HTTP request
+ * cannot be handled
+ * @see javax.servlet.Servlet#service
+ */
+ @Override
+ protected void service(final HttpServletRequest request, final HttpServletResponse response)
+ throws ServletException, IOException {
+ /**
+ * There is an annoying edge case where the service method is
+ * invoked for the case when the URI is equal to the deployment URL
+ * minus the '/', for example http://locahost:8080/HelloWorldWebApp
+ */
+ final String servletPath = request.getServletPath();
+ final StringBuffer requestUrl = request.getRequestURL();
+ final String requestURI = request.getRequestURI();
+
+ // final String pathInfo = request.getPathInfo();
+ // final boolean checkPathInfo = pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/");
+ // if (checkPathInfo && !request.getRequestURI().endsWith("/")) {
+ // Only do this if the last segment of the servlet path does not contain '.'
+ // This handles the case when the extension mapping is used with the servlet
+ // see issue 506
+ // This solution does not require parsing the deployment descriptor,
+ // however still leaves it broken for the very rare case if a standard path
+ // servlet mapping would include dot in the last segment (e.g. /.webresources/*)
+ // and somebody would want to hit the root resource without the trailing slash
+ // final int i = servletPath.lastIndexOf('/');
+ // if (servletPath.substring(i + 1).indexOf('.') < 0) {
+ // TODO (+ handle request URL with invalid characters - see the creation of absoluteUriBuilder below)
+ // if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
+ // URI l = UriBuilder.fromUri(request.getRequestURL().toString()).
+ // path("/").
+ // replaceQuery(request.getQueryString()).build();
+ //
+ // response.setStatus(307);
+ // response.setHeader("Location", l.toASCIIString());
+ // return;
+ // } else {
+ // pathInfo = "/";
+ // requestURL.append("/");
+ // requestURI += "/";
+ // }
+ // }
+ // }
+
+ /**
+ * The HttpServletRequest.getRequestURL() contains the complete URI
+ * minus the query and fragment components.
+ */
+ final UriBuilder absoluteUriBuilder;
+ try {
+ absoluteUriBuilder = UriBuilder.fromUri(requestUrl.toString());
+ } catch (final IllegalArgumentException iae) {
+ setResponseForInvalidUri(response, iae);
+ return;
+ }
+
+ /**
+ * The HttpServletRequest.getPathInfo() and
+ * HttpServletRequest.getServletPath() are in decoded form.
+ *
+ * On some servlet implementations the getPathInfo() removed
+ * contiguous '/' characters. This is problematic if URIs
+ * are embedded, for example as the last path segment.
+ * We need to work around this and not use getPathInfo
+ * for the decodedPath.
+ */
+ final String decodedBasePath = request.getContextPath() + servletPath + "/";
+
+ final String encodedBasePath = UriComponent.encode(decodedBasePath,
+ UriComponent.Type.PATH);
+
+ if (!decodedBasePath.equals(encodedBasePath)) {
+ throw new ProcessingException("The servlet context path and/or the "
+ + "servlet path contain characters that are percent encoded");
+ }
+
+ final URI baseUri;
+ final URI requestUri;
+ try {
+ baseUri = absoluteUriBuilder.replacePath(encodedBasePath).build();
+ String queryParameters = ContainerUtils.encodeUnsafeCharacters(request.getQueryString());
+ if (queryParameters == null) {
+ queryParameters = "";
+ }
+
+ requestUri = absoluteUriBuilder.replacePath(requestURI)
+ .replaceQuery(queryParameters)
+ .build();
+ } catch (final UriBuilderException | IllegalArgumentException ex) {
+ setResponseForInvalidUri(response, ex);
+ return;
+ }
+
+ service(baseUri, requestUri, request, response);
+ }
+
+ private void setResponseForInvalidUri(final HttpServletResponse response, final Throwable throwable) throws IOException {
+ LOGGER.log(Level.FINER, "Error while processing request.", throwable);
+
+ final Response.Status badRequest = Response.Status.BAD_REQUEST;
+ if (webComponent.configSetStatusOverSendError) {
+ response.reset();
+ //noinspection deprecation
+ response.setStatus(badRequest.getStatusCode(), badRequest.getReasonPhrase());
+ } else {
+ response.sendError(badRequest.getStatusCode(), badRequest.getReasonPhrase());
+ }
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+
+ final ContainerLifecycleListener listener = containerListener;
+ if (listener != null) {
+ listener.onShutdown(this);
+ }
+ }
+
+ @Override
+ public void init() throws ServletException {
+ init(new WebServletConfig(this));
+ }
+
+ /**
+ * Dispatch client requests to a resource class.
+ *
+ * @param baseUri the base URI of the request.
+ * @param requestUri the URI of the request.
+ * @param request the {@link javax.servlet.http.HttpServletRequest} object that contains the request the client made to
+ * the Web component.
+ * @param response the {@link javax.servlet.http.HttpServletResponse} object that contains the response the Web component
+ * returns to the client.
+ * @return lazily initialized response status code {@link Value value provider}. If not resolved in the moment of call to
+ * {@link Value#get()}, {@code -1} is returned.
+ * @throws IOException if an input or output error occurs while the Web component is handling the HTTP request.
+ * @throws ServletException if the HTTP request cannot be handled.
+ */
+ public Value<Integer> service(final URI baseUri, final URI requestUri, final HttpServletRequest request,
+ final HttpServletResponse response) throws ServletException, IOException {
+ return webComponent.service(baseUri, requestUri, request, response);
+ }
+
+ /**
+ * Dispatch client requests to a resource class and returns {@link ResponseWriter},
+ * Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter} implementation.
+ *
+ * @param baseUri the base URI of the request.
+ * @param requestUri the URI of the request.
+ * @param request the {@link javax.servlet.http.HttpServletRequest} object that contains the request the client made to
+ * the Web component.
+ * @param response the {@link javax.servlet.http.HttpServletResponse} object that contains the response the Web component
+ * returns to the client.
+ * @return returns {@link ResponseWriter}, Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter}
+ * implementation, into which processed request response was written to.
+ * @throws IOException if an input or output error occurs while the Web component is handling the HTTP request.
+ * @throws ServletException if the HTTP request cannot be handled.
+ */
+ private ResponseWriter serviceImpl(final URI baseUri, final URI requestUri, final HttpServletRequest request,
+ final HttpServletResponse response) throws ServletException, IOException {
+ return webComponent.serviceImpl(baseUri, requestUri, request, response);
+ }
+
+ // Filter
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ this.filterConfig = filterConfig;
+ init(new WebFilterConfig(filterConfig));
+
+ final String regex = (String) getConfiguration().getProperty(ServletProperties.FILTER_STATIC_CONTENT_REGEX);
+ if (regex != null && !regex.isEmpty()) {
+ try {
+ staticContentPattern = Pattern.compile(regex);
+ } catch (final PatternSyntaxException ex) {
+ throw new ContainerException(LocalizationMessages.INIT_PARAM_REGEX_SYNTAX_INVALID(
+ regex, ServletProperties.FILTER_STATIC_CONTENT_REGEX), ex);
+ }
+ }
+
+ this.filterContextPath = filterConfig.getInitParameter(ServletProperties.FILTER_CONTEXT_PATH);
+ if (filterContextPath != null) {
+ if (filterContextPath.isEmpty()) {
+ filterContextPath = null;
+ } else {
+ if (!filterContextPath.startsWith("/")) {
+ filterContextPath = '/' + filterContextPath;
+ }
+ if (filterContextPath.endsWith("/")) {
+ filterContextPath = filterContextPath.substring(0, filterContextPath.length() - 1);
+ }
+ }
+ }
+
+ // get the url-pattern defined (e.g.) in the filter-mapping section of web.xml
+ final FilterUrlMappingsProvider filterUrlMappingsProvider = getFilterUrlMappingsProvider();
+ if (filterUrlMappingsProvider != null) {
+ filterUrlMappings = filterUrlMappingsProvider.getFilterUrlMappings(filterConfig);
+ }
+
+ // we need either the url-pattern from the filter mapping (in case of Servlet 3) or specific init-param to
+ // determine the baseUri and request relative URI. If we do not have either one, the app will most likely
+ // not work (won't be accessible)
+ if (filterUrlMappings == null && filterContextPath == null) {
+ LOGGER.warning(LocalizationMessages.FILTER_CONTEXT_PATH_MISSING());
+ }
+ }
+
+ @Override
+ public void doFilter(final ServletRequest servletRequest,
+ final ServletResponse servletResponse,
+ final FilterChain filterChain)
+ throws IOException, ServletException {
+ try {
+ doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain);
+ } catch (final ClassCastException e) {
+ throw new ServletException("non-HTTP request or response", e);
+ }
+ }
+
+ /**
+ * Get the servlet context for the servlet or filter, depending on
+ * how this class is registered.
+ *
+ * @return the servlet context for the servlet or filter.
+ */
+ @Override
+ public ServletContext getServletContext() {
+ if (filterConfig != null) {
+ return filterConfig.getServletContext();
+ }
+
+ return super.getServletContext();
+ }
+
+ /**
+ * Dispatches client requests to the
+ * {@link #service(URI, URI, HttpServletRequest, HttpServletResponse)} method.
+ * <p />
+ * If the servlet path matches the regular expression declared by the
+ * property {@link ServletProperties#FILTER_STATIC_CONTENT_REGEX} then the
+ * request is forwarded to the next filter in the filter chain so that the
+ * underlying servlet engine can process the request otherwise Jersey
+ * will process the request.
+ *
+ * @param request the {@link HttpServletRequest} object that
+ * contains the request the client made to
+ * the servlet.
+ * @param response the {@link HttpServletResponse} object that
+ * contains the response the servlet returns
+ * to the client.
+ * @param chain the chain of filters from which the next filter can be invoked.
+ * @throws java.io.IOException in case of an I/O error.
+ * @throws javax.servlet.ServletException in case of an error while executing the
+ * filter chain.
+ */
+ public void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
+ if (request.getAttribute("javax.servlet.include.request_uri") != null) {
+ final String includeRequestURI = (String) request.getAttribute("javax.servlet.include.request_uri");
+
+ if (!includeRequestURI.equals(request.getRequestURI())) {
+ doFilter(request, response, chain,
+ includeRequestURI,
+ (String) request.getAttribute("javax.servlet.include.servlet_path"),
+ (String) request.getAttribute("javax.servlet.include.query_string"));
+ return;
+ }
+ }
+
+ /**
+ * JERSEY-880 - WAS interprets HttpServletRequest#getServletPath() and HttpServletRequest#getPathInfo()
+ * differently when accessing a static resource.
+ */
+ final String servletPath = request.getServletPath()
+ + (request.getPathInfo() == null ? "" : request.getPathInfo());
+
+ doFilter(request, response, chain,
+ request.getRequestURI(),
+ servletPath,
+ request.getQueryString());
+ }
+
+ private void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain,
+ final String requestURI, final String servletPath, final String queryString)
+ throws IOException, ServletException {
+ // if we match the static content regular expression lets delegate to
+ // the filter chain to use the default container servlets & handlers
+ final Pattern p = getStaticContentPattern();
+ if (p != null && p.matcher(servletPath).matches()) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ if (filterContextPath != null) {
+ if (!servletPath.startsWith(filterContextPath)) {
+ throw new ContainerException(LocalizationMessages.SERVLET_PATH_MISMATCH(servletPath, filterContextPath));
+ //TODO:
+ // } else if (servletPath.length() == filterContextPath.length()) {
+ // // Path does not end in a slash, may need to redirect
+ // if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
+ // URI l = UriBuilder.fromUri(request.getRequestURL().toString()).
+ // path("/").
+ // replaceQuery(queryString).build();
+ //
+ // response.setStatus(307);
+ // response.setHeader("Location", l.toASCIIString());
+ // return;
+ // } else {
+ // requestURI += "/";
+ // }
+ }
+ }
+
+ final URI baseUri;
+ final URI requestUri;
+ try {
+ final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString());
+
+ // depending on circumstances, use the correct path to replace in the absolute request URI
+ final String pickedUrlMapping = pickUrlMapping(request.getRequestURL().toString(), filterUrlMappings);
+ final String replacingPath = pickedUrlMapping != null
+ ? pickedUrlMapping
+ : (filterContextPath != null ? filterContextPath : "");
+
+ baseUri = absoluteUriBuilder.replacePath(request.getContextPath()).path(replacingPath).path("/").build();
+
+
+ requestUri = absoluteUriBuilder.replacePath(requestURI)
+ .replaceQuery(ContainerUtils.encodeUnsafeCharacters(queryString))
+ .build();
+ } catch (final IllegalArgumentException iae) {
+ setResponseForInvalidUri(response, iae);
+ return;
+ }
+
+ final ResponseWriter responseWriter = serviceImpl(baseUri, requestUri, request, response);
+
+ // If forwarding is configured and response is a 404 with no entity
+ // body then call the next filter in the chain
+
+ if (webComponent.forwardOn404 && !response.isCommitted()) {
+ boolean hasEntity = false;
+ Response.StatusType status = null;
+ if (responseWriter.responseContextResolved()) {
+ final ContainerResponse responseContext = responseWriter.getResponseContext();
+ hasEntity = responseContext.hasEntity();
+ status = responseContext.getStatusInfo();
+ }
+ if (!hasEntity && status == Response.Status.NOT_FOUND) {
+ // lets clear the response to OK before we forward to the next in the chain
+ // as OK is the default set by servlet containers before filters/servlets do any work
+ // so lets hide our footsteps and pretend we were never in the chain at all and let the
+ // next filter or servlet return the 404 if they can't find anything to return
+ //
+ // We could add an optional flag to disable this step if anyone can ever find a case where
+ // this causes a problem, though I suspect any problems will really be with downstream
+ // servlets not correctly setting an error status if they cannot find something to return
+ response.setStatus(HttpServletResponse.SC_OK);
+ chain.doFilter(request, response);
+ }
+ }
+ }
+
+ /**
+ * Picks the most suitable url mapping (in case more than one is defined) based on the request URI.
+ *
+ * @param requestUri String representation of the request URI
+ * @param filterUrlMappings set of configured filter url-patterns
+ * @return the most suitable context path, or {@code null} if empty
+ */
+ private String pickUrlMapping(final String requestUri, final List<String> filterUrlMappings) {
+ if (filterUrlMappings == null || filterUrlMappings.isEmpty()) {
+ return null;
+ }
+
+ if (filterUrlMappings.size() == 1) {
+ return filterUrlMappings.get(0);
+ }
+
+ for (final String pattern : filterUrlMappings) {
+ if (requestUri.contains(pattern)) {
+ return pattern;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Resolve the {@link FilterUrlMappingsProvider} service via hk2.
+ *
+ * Will only work in Servlet 3 container, as the older API version
+ * does not provide access to the filter mapping structure.
+ *
+ * @return {@code FilterContextPath} instance, if available, {@code null} otherwise.
+ */
+ private FilterUrlMappingsProvider getFilterUrlMappingsProvider() {
+ FilterUrlMappingsProvider filterUrlMappingsProvider = null;
+ final Iterator<FilterUrlMappingsProvider> providers = Providers.getAllProviders(
+ getApplicationHandler().getInjectionManager(), FilterUrlMappingsProvider.class).iterator();
+ if (providers.hasNext()) {
+ filterUrlMappingsProvider = providers.next();
+ }
+ return filterUrlMappingsProvider;
+ }
+
+ /**
+ * Get the static content path pattern.
+ *
+ * @return the {@link Pattern} compiled from a regular expression that is
+ * the property value of {@link ServletProperties#FILTER_STATIC_CONTENT_REGEX}.
+ * A {@code null} value will be returned if the property is not set or is
+ * an empty String.
+ */
+ protected Pattern getStaticContentPattern() {
+ return staticContentPattern;
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return webComponent.appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ try {
+ containerListener.onShutdown(this);
+
+ webComponent = new WebComponent(webComponent.webConfig, configuration);
+ containerListener = webComponent.appHandler;
+ containerListener.onReload(this);
+ containerListener.onStartup(this);
+ } catch (final ServletException ex) {
+ LOGGER.log(Level.SEVERE, "Reload failed", ex);
+ }
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return webComponent.appHandler;
+ }
+
+ /**
+ * Get {@link WebComponent} used by this servlet container.
+ *
+ * @return The web component.
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public WebComponent getWebComponent() {
+ return webComponent;
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java
new file mode 100644
index 0000000..e6ea54b
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import org.glassfish.jersey.internal.util.PropertiesClass;
+
+/**
+ * Jersey servlet container configuration properties.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@PropertiesClass
+public final class ServletProperties {
+
+ /**
+ * If set, indicates the URL pattern of the Jersey servlet filter context path.
+ * <p>
+ * If the URL pattern of a filter is set to a base path and a wildcard,
+ * such as "/base/*", then this property can be used to declare a filter
+ * context path that behaves in the same manner as the Servlet context
+ * path for determining the base URI of the application. (Note that with
+ * the Servlet 2.x API it is not possible to determine the URL pattern
+ * without parsing the {@code web.xml}, hence why this property is necessary.)
+ * <p>
+ * The property is only applicable when {@link ServletContainer Jersey servlet
+ * container} is configured to run as a {@link javax.servlet.Filter}, otherwise this property
+ * will be ignored.
+ * <p>
+ * The value of the property may consist of one or more path segments separate by
+ * {@code '/'}.
+ * <p></p>
+ * A default value is not set.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String FILTER_CONTEXT_PATH = "jersey.config.servlet.filter.contextPath";
+
+ /**
+ * If set to {@code true} and a 404 response with no entity body is returned
+ * from either the runtime or the application then the runtime forwards the
+ * request to the next filter in the filter chain. This enables another filter
+ * or the underlying servlet engine to process the request. Before the request
+ * is forwarded the response status is set to 200.
+ * <p>
+ * This property is an alternative to setting a {@link #FILTER_STATIC_CONTENT_REGEX
+ * static content regular expression} and requires less configuration. However,
+ * application code, such as methods corresponding to sub-resource locators,
+ * may be invoked when this feature is enabled.
+ * <p></p>
+ * The property is only applicable when {@link ServletContainer Jersey servlet
+ * container} is configured to run as a {@link javax.servlet.Filter}, otherwise
+ * this property will be ignored.
+ * <p></p>
+ * Application code, such as methods corresponding to sub-resource locators
+ * may be invoked when this feature is enabled.
+ * <p>
+ * The default value is {@code false}.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String FILTER_FORWARD_ON_404 = "jersey.config.servlet.filter.forwardOn404";
+
+ /**
+ * If set the regular expression is used to match an incoming servlet path URI
+ * to some web page content such as static resources or JSPs to be handled
+ * by the underlying servlet engine.
+ * <p></p>
+ * The property is only applicable when {@link ServletContainer Jersey servlet
+ * container} is configured to run as a {@link javax.servlet.Filter}, otherwise
+ * this property will be ignored. If a servlet path matches this regular
+ * expression then the filter forwards the request to the next filter in the
+ * filter chain so that the underlying servlet engine can process the request
+ * otherwise Jersey will process the request. For example if you set the value
+ * to {@code /(image|css)/.*} then you can serve up images and CSS files
+ * for your Implicit or Explicit Views while still processing your JAX-RS
+ * resources.
+ * <p></p>
+ * The type of this property must be a String and the value must be a valid
+ * regular expression.
+ * <p></p>
+ * A default value is not set.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String FILTER_STATIC_CONTENT_REGEX = "jersey.config.servlet.filter.staticContentRegex";
+
+ /**
+ * Application configuration initialization property whose value is a fully
+ * qualified class name of a class that implements {@link javax.ws.rs.core.Application}.
+ * <p></p>
+ * A default value is not set.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ // TODO implement generic support
+ public static final String JAXRS_APPLICATION_CLASS = "javax.ws.rs.Application";
+
+ /**
+ * Indicates that Jersey should scan the whole web app for application-specific resources and
+ * providers. If the property is present and the value is not {@code false}, the whole web app
+ * will be scanned for JAX-RS root resources (annotated with {@link javax.ws.rs.Path @Path})
+ * and providers (annotated with {@link javax.ws.rs.ext.Provider @Provider}).
+ * <p></p>
+ * The property value MUST be an instance of {@link String}. The allowed values are {@code true}
+ * and {@code false}.
+ * <p></p>
+ * A default value is not set.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String PROVIDER_WEB_APP = "jersey.config.servlet.provider.webapp";
+
+ /**
+ * If {@code true} then query parameters will not be treated as form parameters (e.g. injectable using
+ * {@link javax.ws.rs.FormParam}) in case a Form request is processed by server.
+ * <p>
+ * The default value is {@code false}.
+ * </p>
+ * <p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * </p>
+ *
+ * @since 2.16
+ */
+ public static final String QUERY_PARAMS_AS_FORM_PARAMS_DISABLED = "jersey.config.servlet.form.queryParams.disabled";
+
+ /**
+ * Identifies the object that will be used as a parent {@code HK2 ServiceLocator} in the Jersey
+ * {@link WebComponent}.
+ * <p></p>
+ * This property gives a possibility to use HK2 services that are registered and/or created
+ * outside of the Jersey server context.
+ * <p></p>
+ * By default this property is not set.
+ * <p></p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String SERVICE_LOCATOR = "jersey.config.servlet.context.serviceLocator";
+
+ private ServletProperties() {
+ // prevents instantiation
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java
new file mode 100644
index 0000000..7a17521
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.glassfish.jersey.internal.PropertiesDelegate;
+
+/**
+ * @author Martin Matula
+ */
+class ServletPropertiesDelegate implements PropertiesDelegate {
+ private final HttpServletRequest request;
+
+ public ServletPropertiesDelegate(HttpServletRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return request.getAttribute(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ //noinspection unchecked
+ return Collections.list(request.getAttributeNames());
+ }
+
+ @Override
+ public void setProperty(String name, Object object) {
+ request.setAttribute(name, object);
+ }
+
+ @Override
+ public void removeProperty(String name) {
+ request.removeAttribute(name);
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java
new file mode 100644
index 0000000..f80464a
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.server.internal.AbstractResourceFinderAdapter;
+import org.glassfish.jersey.server.internal.scanning.JarFileScanner;
+import org.glassfish.jersey.server.internal.scanning.ResourceFinderException;
+import org.glassfish.jersey.server.internal.scanning.CompositeResourceFinder;
+
+/**
+ * A scanner that recursively scans resources within a Web application.
+ *
+ * @author Paul Sandoz
+ */
+final class WebAppResourcesScanner extends AbstractResourceFinderAdapter {
+
+ private static final String[] paths = new String[] {"/WEB-INF/lib/", "/WEB-INF/classes/"};
+
+ private final ServletContext sc;
+ private CompositeResourceFinder compositeResourceFinder = new CompositeResourceFinder();
+
+ /**
+ * Scan from a set of web resource paths.
+ * <p/>
+ *
+ * @param sc {@link ServletContext}.
+ */
+ WebAppResourcesScanner(final ServletContext sc) {
+ this.sc = sc;
+
+ processPaths(paths);
+ }
+
+ private void processPaths(final String... paths) {
+ for (final String path : paths) {
+
+ final Set<String> resourcePaths = sc.getResourcePaths(path);
+ if (resourcePaths == null) {
+ break;
+ }
+
+ compositeResourceFinder.push(new AbstractResourceFinderAdapter() {
+
+ private final Deque<String> resourcePathsStack = new LinkedList<String>() {
+
+ private static final long serialVersionUID = 3109256773218160485L;
+
+ {
+ for (final String resourcePath : resourcePaths) {
+ push(resourcePath);
+ }
+ }
+ };
+
+ private String current;
+ private String next;
+
+ @Override
+ public boolean hasNext() {
+ while (next == null && !resourcePathsStack.isEmpty()) {
+ next = resourcePathsStack.pop();
+
+ if (next.endsWith("/")) {
+ processPaths(next);
+ next = null;
+ } else if (next.endsWith(".jar")) {
+ try {
+ compositeResourceFinder.push(new JarFileScanner(sc.getResourceAsStream(next), "", true));
+ } catch (final IOException ioe) {
+ throw new ResourceFinderException(ioe);
+ }
+ next = null;
+ }
+ }
+
+ return next != null;
+ }
+
+ @Override
+ public String next() {
+ if (next != null || hasNext()) {
+ current = next;
+ next = null;
+ return current;
+ }
+
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public InputStream open() {
+ return sc.getResourceAsStream(current);
+ }
+
+ @Override
+ public void reset() {
+ throw new UnsupportedOperationException();
+ }
+ });
+
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return compositeResourceFinder.hasNext();
+ }
+
+ @Override
+ public String next() {
+ return compositeResourceFinder.next();
+ }
+
+ @Override
+ public InputStream open() {
+ return compositeResourceFinder.open();
+ }
+
+ @Override
+ public void close() {
+ compositeResourceFinder.close();
+ }
+
+ @Override
+ public void reset() {
+ compositeResourceFinder = new CompositeResourceFinder();
+ processPaths(paths);
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java
new file mode 100644
index 0000000..84962be
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.ServiceFinderBinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
+import org.glassfish.jersey.message.internal.HeaderValueException;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.BackgroundSchedulerLiteral;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.InternalServerProperties;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+import org.glassfish.jersey.servlet.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.PersistenceUnitBinder;
+import org.glassfish.jersey.servlet.internal.ResponseWriter;
+import org.glassfish.jersey.servlet.internal.ServletContainerProviderFactory;
+import org.glassfish.jersey.servlet.internal.Utils;
+import org.glassfish.jersey.servlet.internal.spi.ExtendedServletContainerProvider;
+import org.glassfish.jersey.servlet.internal.spi.RequestContextProvider;
+import org.glassfish.jersey.servlet.internal.spi.RequestScopedInitializerProvider;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider;
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+import org.glassfish.jersey.uri.UriComponent;
+
+/**
+ * An common Jersey web component that may be extended by a Servlet and/or
+ * Filter implementation, or encapsulated by a Servlet or Filter implementation.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class WebComponent {
+
+ private static final Logger LOGGER = Logger.getLogger(WebComponent.class.getName());
+
+ private static final Type REQUEST_TYPE = (new GenericType<Ref<HttpServletRequest>>() {}).getType();
+ private static final Type RESPONSE_TYPE = (new GenericType<Ref<HttpServletResponse>>() {}).getType();
+
+ private static final AsyncContextDelegate DEFAULT_ASYNC_DELEGATE = new AsyncContextDelegate() {
+
+ @Override
+ public void suspend() throws IllegalStateException {
+ throw new UnsupportedOperationException(LocalizationMessages.ASYNC_PROCESSING_NOT_SUPPORTED());
+ }
+
+ @Override
+ public void complete() {
+ }
+ };
+
+ private final RequestScopedInitializerProvider requestScopedInitializer;
+ private final boolean requestResponseBindingExternalized;
+
+ private static final RequestScopedInitializerProvider DEFAULT_REQUEST_SCOPE_INITIALIZER_PROVIDER =
+ context -> (RequestScopedInitializer) injectionManager -> {
+ injectionManager.<Ref<HttpServletRequest>>getInstance(REQUEST_TYPE).set(context.getHttpServletRequest());
+ injectionManager.<Ref<HttpServletResponse>>getInstance(RESPONSE_TYPE).set(context.getHttpServletResponse());
+ };
+
+ /**
+ * Return the first found {@link AsyncContextDelegateProvider}
+ * (via {@link Providers#getAllProviders(InjectionManager, Class)}) or {@code #DEFAULT_ASYNC_DELEGATE} if
+ * other delegate cannot be found.
+ *
+ * @return a non-null AsyncContextDelegateProvider.
+ */
+ private AsyncContextDelegateProvider getAsyncExtensionDelegate() {
+ final Iterator<AsyncContextDelegateProvider> providers = Providers.getAllProviders(appHandler.getInjectionManager(),
+ AsyncContextDelegateProvider.class).iterator();
+ if (providers.hasNext()) {
+ return providers.next();
+ }
+
+ return (request, response) -> DEFAULT_ASYNC_DELEGATE;
+ }
+
+ @SuppressWarnings("JavaDoc")
+ private static class HttpServletRequestReferencingFactory extends ReferencingFactory<HttpServletRequest> {
+
+ @Inject
+ public HttpServletRequestReferencingFactory(final Provider<Ref<HttpServletRequest>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ @SuppressWarnings("JavaDoc")
+ private static class HttpServletResponseReferencingFactory extends ReferencingFactory<HttpServletResponse> {
+
+ @Inject
+ public HttpServletResponseReferencingFactory(final Provider<Ref<HttpServletResponse>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ private final class WebComponentBinder extends AbstractBinder {
+
+ private final Map<String, Object> applicationProperties;
+
+ /**
+ * Create binder for {@link WebComponent} passing a map of properties to determine whether certain features are allowed
+ * or
+ * not.
+ *
+ * @param applicationProperties map of properties to determine whether certain features are allowed or not.
+ */
+ private WebComponentBinder(final Map<String, Object> applicationProperties) {
+ this.applicationProperties = applicationProperties;
+ }
+
+ @Override
+ protected void configure() {
+
+ if (!requestResponseBindingExternalized) {
+
+ // request
+ bindFactory(HttpServletRequestReferencingFactory.class).to(HttpServletRequest.class)
+ .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+
+ bindFactory(ReferencingFactory.referenceFactory())
+ .to(new GenericType<Ref<HttpServletRequest>>() {}).in(RequestScoped.class);
+
+ // response
+ bindFactory(HttpServletResponseReferencingFactory.class).to(HttpServletResponse.class)
+ .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.referenceFactory())
+ .to(new GenericType<Ref<HttpServletResponse>>() {}).in(RequestScoped.class);
+ }
+
+ bindFactory(webConfig::getServletContext).to(ServletContext.class).in(Singleton.class);
+
+ final ServletConfig servletConfig = webConfig.getServletConfig();
+ if (webConfig.getConfigType() == WebConfig.ConfigType.ServletConfig) {
+ bindFactory(() -> servletConfig).to(ServletConfig.class).in(Singleton.class);
+
+ // @PersistenceUnit
+ final Enumeration initParams = servletConfig.getInitParameterNames();
+ while (initParams.hasMoreElements()) {
+ final String initParamName = (String) initParams.nextElement();
+
+ if (initParamName.startsWith(PersistenceUnitBinder.PERSISTENCE_UNIT_PREFIX)) {
+ install(new PersistenceUnitBinder(servletConfig));
+ break;
+ }
+ }
+ } else {
+ bindFactory(webConfig::getFilterConfig).to(FilterConfig.class).in(Singleton.class);
+ }
+
+ bindFactory(() -> webConfig).to(WebConfig.class).in(Singleton.class);
+
+ install(new ServiceFinderBinder<>(AsyncContextDelegateProvider.class, applicationProperties, RuntimeType.SERVER));
+ install(new ServiceFinderBinder<>(FilterUrlMappingsProvider.class, applicationProperties, RuntimeType.SERVER));
+ }
+ }
+
+ /**
+ * Jersey application handler.
+ */
+ final ApplicationHandler appHandler;
+
+ /**
+ * Jersey background task scheduler - used for scheduling request timeout event handling tasks.
+ */
+ final ScheduledExecutorService backgroundTaskScheduler;
+
+ /**
+ * Web component configuration.
+ */
+ final WebConfig webConfig;
+
+ /**
+ * If {@code true} and deployed as filter, the unmatched requests will be forwarded.
+ */
+ final boolean forwardOn404;
+
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+ * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+ */
+ final boolean configSetStatusOverSendError;
+
+ /**
+ * Asynchronous context delegate provider.
+ */
+ private final AsyncContextDelegateProvider asyncExtensionDelegate;
+
+ /**
+ * Flag whether query parameters should be kept as entity form params if a servlet filter consumes entity and
+ * Jersey has to retrieve form params from servlet request parameters.
+ */
+ private final boolean queryParamsAsFormParams;
+
+ /**
+ * Create and initialize new web component instance.
+ *
+ * @param webConfig we component configuration.
+ * @param resourceConfig Jersey application configuration.
+ * @throws ServletException in case the Jersey application cannot be created from the supplied
+ * resource configuration.
+ */
+ public WebComponent(final WebConfig webConfig, ResourceConfig resourceConfig) throws ServletException {
+
+ this.webConfig = webConfig;
+
+ if (resourceConfig == null) {
+ resourceConfig = createResourceConfig(webConfig);
+ }
+
+
+ final ServletContainerProvider[] allServletContainerProviders =
+ ServletContainerProviderFactory.getAllServletContainerProviders();
+
+ // SPI/extension hook to configure ResourceConfig
+ configure(resourceConfig, allServletContainerProviders);
+
+ boolean rrbExternalized = false;
+ RequestScopedInitializerProvider rsiProvider = null;
+
+ for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+ if (servletContainerProvider instanceof ExtendedServletContainerProvider) {
+ final ExtendedServletContainerProvider extendedProvider =
+ (ExtendedServletContainerProvider) servletContainerProvider;
+
+ if (extendedProvider.bindsServletRequestResponse()) {
+ rrbExternalized = true;
+ }
+ if (rsiProvider == null) { // try to take the first non-null provider
+ rsiProvider = extendedProvider.getRequestScopedInitializerProvider();
+ }
+ }
+ }
+
+ requestScopedInitializer = rsiProvider != null ? rsiProvider : DEFAULT_REQUEST_SCOPE_INITIALIZER_PROVIDER;
+ requestResponseBindingExternalized = rrbExternalized;
+
+ final AbstractBinder webComponentBinder = new WebComponentBinder(resourceConfig.getProperties());
+ resourceConfig.register(webComponentBinder);
+
+ final Object locator = webConfig.getServletContext()
+ .getAttribute(ServletProperties.SERVICE_LOCATOR);
+
+ this.appHandler = new ApplicationHandler(resourceConfig, webComponentBinder, locator);
+
+ this.asyncExtensionDelegate = getAsyncExtensionDelegate();
+ this.forwardOn404 = webConfig.getConfigType() == WebConfig.ConfigType.FilterConfig
+ && resourceConfig.isProperty(ServletProperties.FILTER_FORWARD_ON_404);
+ this.queryParamsAsFormParams = !resourceConfig.isProperty(ServletProperties.QUERY_PARAMS_AS_FORM_PARAMS_DISABLED);
+ this.configSetStatusOverSendError = ServerProperties.getValue(resourceConfig.getProperties(),
+ ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+ this.backgroundTaskScheduler = appHandler.getInjectionManager()
+ .getInstance(ScheduledExecutorService.class, BackgroundSchedulerLiteral.INSTANCE);
+ }
+
+ /**
+ * Dispatch client requests to a resource class.
+ *
+ * @param baseUri the base URI of the request.
+ * @param requestUri the URI of the request.
+ * @param servletRequest the {@link javax.servlet.http.HttpServletRequest} object that
+ * contains the request the client made to
+ * the Web component.
+ * @param servletResponse the {@link javax.servlet.http.HttpServletResponse} object that
+ * contains the response the Web component returns
+ * to the client.
+ * @return lazily initialized response status code {@link Value value provider}. If not resolved in the moment of call to
+ * {@link Value#get()}, {@code -1} is returned.
+ * @throws java.io.IOException if an input or output error occurs
+ * while the Web component is handling the
+ * HTTP request.
+ * @throws javax.servlet.ServletException if the HTTP request cannot be handled.
+ */
+ public Value<Integer> service(
+ final URI baseUri,
+ final URI requestUri,
+ final HttpServletRequest servletRequest,
+ final HttpServletResponse servletResponse) throws ServletException, IOException {
+ final ResponseWriter responseWriter = serviceImpl(baseUri, requestUri, servletRequest, servletResponse);
+ return Values.lazy(new Value<Integer>() {
+ @Override
+ public Integer get() {
+ return responseWriter.responseContextResolved() ? responseWriter.getResponseStatus() : -1;
+ }
+ });
+ }
+
+ /**
+ * Dispatch client requests to a resource class.
+ *
+ * @param baseUri the base URI of the request.
+ * @param requestUri the URI of the request.
+ * @param servletRequest the {@link javax.servlet.http.HttpServletRequest} object that
+ * contains the request the client made to
+ * the Web component.
+ * @param servletResponse the {@link javax.servlet.http.HttpServletResponse} object that
+ * contains the response the Web component returns
+ * to the client.
+ * @return returns {@link ResponseWriter}, Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter}
+ * implementation, into which processed request response was written to.
+ * @throws java.io.IOException if an input or output error occurs
+ * while the Web component is handling the
+ * HTTP request.
+ * @throws javax.servlet.ServletException if the HTTP request cannot be handled.
+ */
+ /* package */ ResponseWriter serviceImpl(
+ final URI baseUri,
+ final URI requestUri,
+ final HttpServletRequest servletRequest,
+ final HttpServletResponse servletResponse) throws ServletException, IOException {
+
+ final ResponseWriter responseWriter = new ResponseWriter(
+ forwardOn404,
+ configSetStatusOverSendError,
+ servletResponse,
+ asyncExtensionDelegate.createDelegate(servletRequest, servletResponse),
+ backgroundTaskScheduler);
+
+ try {
+ final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri, servletRequest.getMethod(),
+ getSecurityContext(servletRequest), new ServletPropertiesDelegate(servletRequest));
+
+ initContainerRequest(requestContext, servletRequest, servletResponse, responseWriter);
+
+ appHandler.handle(requestContext);
+ } catch (final HeaderValueException hve) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, LocalizationMessages.HEADER_VALUE_READ_FAILED(), hve);
+ }
+
+ final Response.Status status = Response.Status.BAD_REQUEST;
+
+ if (configSetStatusOverSendError) {
+ servletResponse.reset();
+ //noinspection deprecation
+ servletResponse.setStatus(status.getStatusCode(), status.getReasonPhrase());
+ } else {
+ servletResponse.sendError(status.getStatusCode(), status.getReasonPhrase());
+ }
+ } catch (final Exception e) {
+ throw new ServletException(e);
+ }
+ return responseWriter;
+ }
+
+ /**
+ * Initialize {@code ContainerRequest} instance to used used to handle {@code servletRequest}.
+ */
+ private void initContainerRequest(
+ final ContainerRequest requestContext,
+ final HttpServletRequest servletRequest,
+ final HttpServletResponse servletResponse,
+ final ResponseWriter responseWriter) throws IOException {
+
+ requestContext.setEntityStream(servletRequest.getInputStream());
+ requestContext.setRequestScopedInitializer(requestScopedInitializer.get(new RequestContextProvider() {
+ @Override
+ public HttpServletRequest getHttpServletRequest() {
+ return servletRequest;
+ }
+ @Override
+ public HttpServletResponse getHttpServletResponse() {
+ return servletResponse;
+ }
+ }));
+ requestContext.setWriter(responseWriter);
+
+ addRequestHeaders(servletRequest, requestContext);
+ // Check if any servlet filters have consumed a request entity
+ // of the media type application/x-www-form-urlencoded
+ // This can happen if a filter calls request.getParameter(...)
+ filterFormParameters(servletRequest, requestContext);
+ }
+
+ /**
+ * Get default {@link javax.ws.rs.core.SecurityContext} for given {@code request}.
+ *
+ * @param request http servlet request to create a security context for.
+ * @return a non-null security context instance.
+ */
+ private static SecurityContext getSecurityContext(final HttpServletRequest request) {
+ return new SecurityContext() {
+
+ @Override
+ public Principal getUserPrincipal() {
+ return request.getUserPrincipal();
+ }
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return request.isUserInRole(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.isSecure();
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return request.getAuthType();
+ }
+ };
+ }
+
+ /**
+ * Create a {@link ResourceConfig} instance from given {@link WebConfig}.
+ *
+ * @param config web config to create resource config from.
+ * @return resource config instance.
+ * @throws ServletException if an error has occurred.
+ */
+ private static ResourceConfig createResourceConfig(final WebConfig config) throws ServletException {
+ final ServletContext servletContext = config.getServletContext();
+
+ // check if ResourceConfig has already been created, if so use it
+ ResourceConfig resourceConfig = Utils.retrieve(config.getServletContext(), config.getName());
+ if (resourceConfig != null) {
+ return resourceConfig;
+ }
+
+ final Map<String, Object> initParams = getInitParams(config);
+ final Map<String, Object> contextParams = Utils.getContextParams(servletContext);
+
+ // check if the JAX-RS application config class property is present
+ final String jaxrsApplicationClassName = config.getInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS);
+
+ if (jaxrsApplicationClassName == null) {
+ // If no resource config class property is present, create default config
+ resourceConfig = new ResourceConfig().addProperties(initParams).addProperties(contextParams);
+
+ final String webApp = config.getInitParameter(ServletProperties.PROVIDER_WEB_APP);
+ if (webApp != null && !"false".equals(webApp)) {
+ resourceConfig.registerFinder(new WebAppResourcesScanner(servletContext));
+ }
+ return resourceConfig;
+ }
+
+ try {
+ final Class<? extends javax.ws.rs.core.Application> jaxrsApplicationClass = AccessController.doPrivileged(
+ ReflectionHelper.<javax.ws.rs.core.Application>classForNameWithExceptionPEA(jaxrsApplicationClassName)
+ );
+
+ if (javax.ws.rs.core.Application.class.isAssignableFrom(jaxrsApplicationClass)) {
+ return ResourceConfig.forApplicationClass(jaxrsApplicationClass)
+ .addProperties(initParams).addProperties(contextParams);
+ } else {
+ throw new ServletException(LocalizationMessages.RESOURCE_CONFIG_PARENT_CLASS_INVALID(
+ jaxrsApplicationClassName, javax.ws.rs.core.Application.class));
+ }
+ } catch (final PrivilegedActionException e) {
+ throw new ServletException(
+ LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_LOAD(jaxrsApplicationClassName), e.getCause());
+ } catch (final ClassNotFoundException e) {
+ throw new ServletException(LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_LOAD(jaxrsApplicationClassName), e);
+ }
+ }
+
+ /**
+ * SPI/extension hook to configure ResourceConfig.
+ *
+ * @param resourceConfig Jersey application configuration.
+ * @throws ServletException if an error has occurred.
+ */
+ private void configure(final ResourceConfig resourceConfig,
+ final ServletContainerProvider[] allServletContainerProviders) throws ServletException {
+
+ for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+ servletContainerProvider.configure(resourceConfig);
+ }
+ }
+
+ /**
+ * Copy request headers present in {@code request} into {@code requestContext} ignoring {@code null} values.
+ *
+ * @param request http servlet request to copy headers from.
+ * @param requestContext container request to copy headers to.
+ */
+ @SuppressWarnings("unchecked")
+ private void addRequestHeaders(final HttpServletRequest request, final ContainerRequest requestContext) {
+ final Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ final String name = names.nextElement();
+
+ final Enumeration<String> values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ final String value = values.nextElement();
+ if (value != null) { // filter out null values
+ requestContext.header(name, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Extract init params from {@link WebConfig}.
+ *
+ * @param webConfig actual servlet context.
+ * @return map representing current init parameters.
+ */
+ private static Map<String, Object> getInitParams(final WebConfig webConfig) {
+ final Map<String, Object> props = new HashMap<>();
+ final Enumeration names = webConfig.getInitParameterNames();
+ while (names.hasMoreElements()) {
+ final String name = (String) names.nextElement();
+ props.put(name, webConfig.getInitParameter(name));
+ }
+ return props;
+ }
+
+ /**
+ * Extract parameters contained in {@link HttpServletRequest servlet request} and put them into
+ * {@link ContainerRequest container request} under
+ * {@value org.glassfish.jersey.server.internal.InternalServerProperties#FORM_DECODED_PROPERTY} property (as {@link Form}
+ * instance).
+ *
+ * @param servletRequest http servlet request to extract params from.
+ * @param containerRequest container request to put {@link Form} property to.
+ */
+ private void filterFormParameters(final HttpServletRequest servletRequest, final ContainerRequest containerRequest) {
+ if (MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE, containerRequest.getMediaType())
+ && !containerRequest.hasEntity()) {
+ final Form form = new Form();
+ final Enumeration parameterNames = servletRequest.getParameterNames();
+
+ final String queryString = servletRequest.getQueryString();
+ final List<String> queryParams = queryString != null ? getDecodedQueryParamList(queryString)
+ : Collections.<String>emptyList();
+
+ final boolean keepQueryParams = queryParamsAsFormParams || queryParams.isEmpty();
+ final MultivaluedMap<String, String> formMap = form.asMap();
+
+ while (parameterNames.hasMoreElements()) {
+ final String name = (String) parameterNames.nextElement();
+ final List<String> values = Arrays.asList(servletRequest.getParameterValues(name));
+
+ formMap.put(name, keepQueryParams ? values : filterQueryParams(name, values, queryParams));
+ }
+
+ if (!formMap.isEmpty()) {
+ containerRequest.setProperty(InternalServerProperties.FORM_DECODED_PROPERTY, form);
+
+ if (LOGGER.isLoggable(Level.WARNING)) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.FORM_PARAM_CONSUMED(containerRequest.getRequestUri()));
+ }
+ }
+ }
+ }
+
+ private List<String> getDecodedQueryParamList(final String queryString) {
+ final List<String> params = new ArrayList<>();
+ for (final String param : queryString.split("&")) {
+ params.add(UriComponent.decode(param, UriComponent.Type.QUERY_PARAM));
+ }
+ return params;
+ }
+
+ /**
+ * From given list of values remove values that represents values of query params of the same name as the processed form
+ * parameter.
+ *
+ * @param name name of form/query parameter.
+ * @param values values of form/query parameter.
+ * @param params collection of unprocessed query parameters.
+ * @return list of form param values for given name without values of query param of the same name.
+ */
+ private List<String> filterQueryParams(final String name, final List<String> values, final Collection<String> params) {
+ return values.stream()
+ .filter(s -> !params.remove(name + "=" + s) && !params.remove(name + "[]=" + s))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get {@link ApplicationHandler} used by this web component.
+ *
+ * @return The application handler
+ */
+ public ApplicationHandler getAppHandler() {
+ return appHandler;
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java
new file mode 100644
index 0000000..bb469c6
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * The Web configuration for accessing initialization parameters of a Web
+ * component and the {@link ServletContext}.
+ *
+ * @author Paul Sandoz
+ */
+public interface WebConfig {
+
+ /**
+ * The web configuration type.
+ */
+ public static enum ConfigType {
+ /**
+ * A configuration type of servlet configuration.
+ */
+ ServletConfig,
+ /**
+ * A configuration type of filter configuration.
+ */
+ FilterConfig
+ }
+
+ /**
+ * Get the configuration type of this config.
+ *
+ * @return the configuration type.
+ */
+ ConfigType getConfigType();
+
+ /**
+ * Get the corresponding ServletConfig if this WebConfig represents a {@link ServletConfig}
+ *
+ * @return servlet config or null
+ */
+ ServletConfig getServletConfig();
+
+ /**
+ * Get the corresponding FilterConfig if this WebConfig represents a {@link FilterConfig}
+ *
+ * @return filter config or null
+ */
+ FilterConfig getFilterConfig();
+
+ /**
+ * Get the name of the Web component.
+ *
+ * @return the name of the Web component.
+ */
+ String getName();
+
+ /**
+ * Get an initialization parameter.
+ *
+ * @param name the parameter name.
+ * @return the parameter value, or null if the parameter is not present.
+ */
+ String getInitParameter(String name);
+
+ /**
+ * Get the enumeration of initialization parameter names.
+ *
+ * @return the enumeration of initialization parameter names.
+ */
+ Enumeration getInitParameterNames();
+
+ /**
+ * Get the {@link ServletContext}.
+ *
+ * @return the {@link ServletContext}.
+ */
+ ServletContext getServletContext();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java
new file mode 100644
index 0000000..d375b85
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * A filter based web config. Delegates all invocations to the filter
+ * configuration from the servlet api.
+ *
+ * @author Paul Sandoz
+ * @author Guilherme Silveira
+ */
+public final class WebFilterConfig implements WebConfig {
+
+ private final FilterConfig filterConfig;
+
+ public WebFilterConfig(final FilterConfig filterConfig) {
+ this.filterConfig = filterConfig;
+ }
+
+ @Override
+ public WebConfig.ConfigType getConfigType() {
+ return WebConfig.ConfigType.FilterConfig;
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return null;
+ }
+
+ @Override
+ public FilterConfig getFilterConfig() {
+ return filterConfig;
+ }
+
+ @Override
+ public String getName() {
+ return filterConfig.getFilterName();
+ }
+
+ @Override
+ public String getInitParameter(final String name) {
+ return filterConfig.getInitParameter(name);
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ return filterConfig.getInitParameterNames();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return filterConfig.getServletContext();
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java
new file mode 100644
index 0000000..5c2a7a7
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * A servlet based web config. Delegates all invocations to the servlet
+ * configuration from the servlet api.
+ *
+ * @author Paul Sandoz
+ * @author guilherme silveira
+ */
+public final class WebServletConfig implements WebConfig {
+
+ private final ServletContainer servlet;
+
+ public WebServletConfig(final ServletContainer servlet) {
+ this.servlet = servlet;
+ }
+
+ @Override
+ public WebConfig.ConfigType getConfigType() {
+ return WebConfig.ConfigType.ServletConfig;
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return servlet.getServletConfig();
+ }
+
+ @Override
+ public FilterConfig getFilterConfig() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return servlet.getServletName();
+ }
+
+ @Override
+ public String getInitParameter(final String name) {
+ return servlet.getInitParameter(name);
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ return servlet.getInitParameterNames();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return servlet.getServletContext();
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java
new file mode 100644
index 0000000..f1795c6
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2013, 2018 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.servlet.internal;
+
+import java.lang.reflect.Proxy;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceUnit;
+import javax.servlet.ServletConfig;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Injectee;
+import org.glassfish.jersey.internal.inject.InjectionResolver;
+import org.glassfish.jersey.server.ContainerException;
+
+/**
+ * {@link PersistenceUnit Persistence unit} injection binder.
+ *
+ * @author Michal Gajdos
+ */
+public class PersistenceUnitBinder extends AbstractBinder {
+
+ private final ServletConfig servletConfig;
+
+ /**
+ * Prefix of the persistence unit init param.
+ */
+ public static final String PERSISTENCE_UNIT_PREFIX = "unit:";
+
+ /**
+ * Creates a new binder for {@link PersistenceUnitInjectionResolver}.
+ *
+ * @param servletConfig servlet config to find persistence units.
+ */
+ public PersistenceUnitBinder(ServletConfig servletConfig) {
+ this.servletConfig = servletConfig;
+ }
+
+ @Singleton
+ private static class PersistenceUnitInjectionResolver implements InjectionResolver<PersistenceUnit> {
+
+ private final Map<String, String> persistenceUnits = new HashMap<>();
+
+ private PersistenceUnitInjectionResolver(ServletConfig servletConfig) {
+ for (final Enumeration parameterNames = servletConfig.getInitParameterNames(); parameterNames.hasMoreElements(); ) {
+ final String key = (String) parameterNames.nextElement();
+
+ if (key.startsWith(PERSISTENCE_UNIT_PREFIX)) {
+ persistenceUnits.put(key.substring(PERSISTENCE_UNIT_PREFIX.length()),
+ "java:comp/env/" + servletConfig.getInitParameter(key));
+ }
+ }
+ }
+
+ @Override
+ public Object resolve(Injectee injectee) {
+ if (!injectee.getRequiredType().equals(EntityManagerFactory.class)) {
+ return null;
+ }
+
+ final PersistenceUnit annotation = injectee.getParent().getAnnotation(PersistenceUnit.class);
+ final String unitName = annotation.unitName();
+
+ if (!persistenceUnits.containsKey(unitName)) {
+ throw new ContainerException(LocalizationMessages.PERSISTENCE_UNIT_NOT_CONFIGURED(unitName));
+ }
+
+ return Proxy.newProxyInstance(
+ this.getClass().getClassLoader(),
+ new Class[] {EntityManagerFactory.class},
+ new ThreadLocalNamedInvoker<EntityManagerFactory>(persistenceUnits.get(unitName)));
+ }
+
+ @Override
+ public boolean isConstructorParameterIndicator() {
+ return false;
+ }
+
+ @Override
+ public boolean isMethodParameterIndicator() {
+ return false;
+ }
+
+ @Override
+ public Class<PersistenceUnit> getAnnotation() {
+ return PersistenceUnit.class;
+ }
+ }
+
+ @Override
+ protected void configure() {
+ bind(new PersistenceUnitInjectionResolver(servletConfig))
+ .to(new GenericType<InjectionResolver<PersistenceUnit>>() {});
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java
new file mode 100644
index 0000000..b96ebb7
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.internal.JerseyRequestTimeoutHandler;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+
+/**
+ * An internal implementation of {@link ContainerResponseWriter} for Servlet containers.
+ * The writer depends on provided {@link AsyncContextDelegate} to support async functionality.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class ResponseWriter implements ContainerResponseWriter {
+
+ private static final Logger LOGGER = Logger.getLogger(ResponseWriter.class.getName());
+
+ private final HttpServletResponse response;
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.servlet.ServletProperties#FILTER_FORWARD_ON_404}.
+ */
+ private final boolean useSetStatusOn404;
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+ * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+ */
+ private final boolean configSetStatusOverSendError;
+ private final CompletableFuture<ContainerResponse> responseContext;
+ private final AsyncContextDelegate asyncExt;
+
+ private final JerseyRequestTimeoutHandler requestTimeoutHandler;
+
+ /**
+ * Creates a new instance to write a single Jersey response.
+ *
+ * @param useSetStatusOn404 true if status should be written explicitly when 404 is returned
+ * @param configSetStatusOverSendError if {@code true} method {@link HttpServletResponse#setStatus} is used over
+ * {@link HttpServletResponse#sendError}
+ * @param response original HttpResponseRequest
+ * @param asyncExt delegate to use for async features implementation
+ * @param timeoutTaskExecutor Jersey runtime executor used for background execution of timeout
+ * handling tasks.
+ */
+ public ResponseWriter(final boolean useSetStatusOn404,
+ final boolean configSetStatusOverSendError,
+ final HttpServletResponse response,
+ final AsyncContextDelegate asyncExt,
+ final ScheduledExecutorService timeoutTaskExecutor) {
+ this.useSetStatusOn404 = useSetStatusOn404;
+ this.configSetStatusOverSendError = configSetStatusOverSendError;
+ this.response = response;
+ this.asyncExt = asyncExt;
+ this.responseContext = new CompletableFuture<>();
+
+ this.requestTimeoutHandler = new JerseyRequestTimeoutHandler(this, timeoutTaskExecutor);
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+ try {
+ // Suspend the servlet.
+ asyncExt.suspend();
+ } catch (final IllegalStateException ex) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.SERVLET_REQUEST_SUSPEND_FAILED(), ex);
+ return false;
+ }
+ // Suspend the internal request timeout handler.
+ return requestTimeoutHandler.suspend(timeOut, timeUnit, timeoutHandler);
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+ requestTimeoutHandler.setSuspendTimeout(timeOut, timeUnit);
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse responseContext)
+ throws ContainerException {
+ this.responseContext.complete(responseContext);
+
+ // first set the content length, so that if headers have an explicit value, it takes precedence over this one
+ if (responseContext.hasEntity() && contentLength != -1 && contentLength < Integer.MAX_VALUE) {
+ response.setContentLength((int) contentLength);
+ }
+ // Note that the writing of headers MUST be performed before
+ // the invocation of sendError as on some Servlet implementations
+ // modification of the response headers will have no effect
+ // after the invocation of sendError.
+ final MultivaluedMap<String, String> headers = getResponseContext().getStringHeaders();
+ for (final Map.Entry<String, List<String>> e : headers.entrySet()) {
+ final Iterator<String> it = e.getValue().iterator();
+ if (!it.hasNext()) {
+ continue;
+ }
+ final String header = e.getKey();
+ if (response.containsHeader(header)) {
+ // replace any headers previously set with values from Jersey container response.
+ response.setHeader(header, it.next());
+ }
+
+ while (it.hasNext()) {
+ response.addHeader(header, it.next());
+ }
+ }
+
+ final String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+ if (reasonPhrase != null) {
+ response.setStatus(responseContext.getStatus(), reasonPhrase);
+ } else {
+ response.setStatus(responseContext.getStatus());
+ }
+
+ if (!responseContext.hasEntity()) {
+ return null;
+ } else {
+ try {
+ final OutputStream outputStream = response.getOutputStream();
+
+ // delegating output stream prevents closing the underlying servlet output stream,
+ // so that any Servlet filters in the chain can still write to the response after us.
+ return new NonCloseableOutputStreamWrapper(outputStream);
+ } catch (final IOException e) {
+ throw new ContainerException(e);
+ }
+ }
+ }
+
+ @Override
+ public void commit() {
+ try {
+ callSendError();
+ } finally {
+ requestTimeoutHandler.close();
+ asyncExt.complete();
+ }
+ }
+
+ /**
+ * According to configuration and response processing status it calls {@link HttpServletResponse#sendError(int, String)} over
+ * common {@link HttpServletResponse#setStatus(int)}.
+ */
+ private void callSendError() {
+ // call HttpServletResponse.sendError in case:
+ // - property ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR == false (default)
+ // - response NOT yet committed
+ // - response entity NOT set
+ // - response status code is 4xx or 5xx
+ // plus in case of Jersey as a Filter (JaaF):
+ // - response status code is 404 (Not Found)
+ // - property ServletProperties#FILTER_FORWARD_ON_404 == false (default)
+ if (!configSetStatusOverSendError && !response.isCommitted()) {
+ final ContainerResponse responseContext = getResponseContext();
+ final boolean hasEntity = responseContext.hasEntity();
+ final Response.StatusType status = responseContext.getStatusInfo();
+ if (!hasEntity && status != null && status.getStatusCode() >= 400
+ && !(useSetStatusOn404 && status == Response.Status.NOT_FOUND)) {
+ final String reason = status.getReasonPhrase();
+ try {
+ if (reason == null || reason.isEmpty()) {
+ response.sendError(status.getStatusCode());
+ } else {
+ response.sendError(status.getStatusCode(), reason);
+ }
+ } catch (final IOException ex) {
+ throw new ContainerException(
+ LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(status, reason != null ? reason : "--"),
+ ex);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void failure(final Throwable error) {
+ try {
+ if (!response.isCommitted()) {
+ try {
+ if (configSetStatusOverSendError) {
+ response.reset();
+ //noinspection deprecation
+ response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed.");
+ } else {
+ response.sendError(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed.");
+ }
+ } catch (final IllegalStateException ex) {
+ // a race condition externally committing the response can still occur...
+ LOGGER.log(Level.FINER, "Unable to reset failed response.", ex);
+ } catch (final IOException ex) {
+ throw new ContainerException(LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed."), ex);
+ } finally {
+ asyncExt.complete();
+ }
+ }
+ } finally {
+ requestTimeoutHandler.close();
+ rethrow(error);
+ }
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true;
+ }
+
+ /**
+ * Rethrow the original exception as required by JAX-RS, 3.3.4
+ *
+ * @param error throwable to be re-thrown
+ */
+ private void rethrow(final Throwable error) {
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+
+ /**
+ * Provides response status captured when
+ * {@link #writeResponseStatusAndHeaders(long, org.glassfish.jersey.server.ContainerResponse)} has been invoked.
+ * The method will block if the write method has not been called yet.
+ *
+ * @return response status
+ */
+ public int getResponseStatus() {
+ return getResponseContext().getStatus();
+ }
+
+ public boolean responseContextResolved() {
+ return responseContext.isDone();
+ }
+
+ public ContainerResponse getResponseContext() {
+ try {
+ return responseContext.get();
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new ContainerException(ex);
+ }
+ }
+
+ private static class NonCloseableOutputStreamWrapper extends OutputStream {
+
+ private final OutputStream delegate;
+
+ public NonCloseableOutputStreamWrapper(final OutputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ delegate.write(b, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ // do not close - let the servlet container close the stream
+ }
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java
new file mode 100644
index 0000000..8dbeb80
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 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.servlet.internal;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+
+/**
+ * Factory class to get all "registered" implementations of {@link ServletContainerProvider}.
+ * Mentioned implementation classes are registered by {@code META-INF/services}.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @since 2.1
+ */
+public final class ServletContainerProviderFactory {
+
+ private ServletContainerProviderFactory() {
+ }
+
+ /**
+ * Returns array of all "registered" implementations of {@link ServletContainerProvider}.
+ *
+ * @return Array of registered providers. Never returns {@code null}.
+ * If there is no implementation registered empty array is returned.
+ */
+ public static ServletContainerProvider[] getAllServletContainerProviders() {
+ // TODO Instances of ServletContainerProvider could be cached, maybe. ???
+ // TODO Check if META-INF/services lookup is enabled. ???
+ return ServiceFinder.find(ServletContainerProvider.class).toArray();
+ }
+
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java
new file mode 100644
index 0000000..9ef4724
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 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.servlet.internal;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A proxy invocation handler that delegates all methods to a thread local instance.
+ *
+ * @author Paul Sandoz
+ */
+public class ThreadLocalInvoker<T> implements InvocationHandler {
+
+ private ThreadLocal<T> threadLocalInstance = new ThreadLocal<>();
+
+ public void set(final T threadLocalInstance) {
+ this.threadLocalInstance.set(threadLocalInstance);
+ }
+
+ public T get() {
+ return this.threadLocalInstance.get();
+ }
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (threadLocalInstance.get() == null) {
+ throw new IllegalStateException(LocalizationMessages.PERSISTENCE_UNIT_NOT_CONFIGURED(proxy.getClass()));
+ }
+
+ try {
+ return method.invoke(threadLocalInstance.get(), args);
+ } catch (final IllegalAccessException ex) {
+ throw new IllegalStateException(ex);
+ } catch (final InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java
new file mode 100644
index 0000000..f8acc93
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, 2018 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.servlet.internal;
+
+import java.lang.reflect.Method;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+/**
+ * A proxy invocation handler that delegates all methods to a thread local instance from JNDI.
+ *
+ * @author Paul Sandoz
+ */
+public class ThreadLocalNamedInvoker<T> extends ThreadLocalInvoker<T> {
+
+ private final String name;
+
+ /**
+ * Create an instance.
+ *
+ * @param name the JNDI name at which an instance of T can be found.
+ */
+ public ThreadLocalNamedInvoker(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // if no instance yet exists for the current thread then look one up and stash it
+ if (this.get() == null) {
+ Context ctx = new InitialContext();
+ T t = (T) ctx.lookup(name);
+ this.set(t);
+ }
+ return super.invoke(proxy, method, args);
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java
new file mode 100644
index 0000000..c125116
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014, 2018 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.servlet.internal;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Utility class.
+ *
+ * @author Michal Gajdos
+ */
+public final class Utils {
+
+ /**
+ * Internal {@link javax.servlet.ServletContext servlet context} attribute name under which an instance of
+ * {@link org.glassfish.jersey.server.ResourceConfig resource config} can be stored. The instance is later used to initialize
+ * servlet in {@link org.glassfish.jersey.servlet.WebConfig} instead of creating a new one.
+ */
+ private static final String RESOURCE_CONFIG = "jersey.config.servlet.internal.resourceConfig";
+
+ /**
+ * Store {@link org.glassfish.jersey.server.ResourceConfig resource config} as an attribute of given
+ * {@link javax.servlet.ServletContext servlet context}. If {@code config} is {@code null} then the previously stored value
+ * (if any) is removed. The {@code configName} is used as an attribute name suffix.
+ *
+ * @param config resource config to be stored.
+ * @param context servlet context to store the config in.
+ * @param configName name or id of the resource config.
+ */
+ public static void store(final ResourceConfig config, final ServletContext context, final String configName) {
+ final String attributeName = RESOURCE_CONFIG + "_" + configName;
+ context.setAttribute(attributeName, config);
+ }
+
+ /**
+ * Load {@link org.glassfish.jersey.server.ResourceConfig resource config} from given
+ * {@link javax.servlet.ServletContext servlet context}. If found then the resource config is also removed from servlet
+ * context. The {@code configName} is used as an attribute name suffix.
+ *
+ * @param context servlet context to load resource config from.
+ * @param configName name or id of the resource config.
+ * @return previously stored resource config or {@code null} if no resource config has been stored.
+ */
+ public static ResourceConfig retrieve(final ServletContext context, final String configName) {
+ final String attributeName = RESOURCE_CONFIG + "_" + configName;
+ final ResourceConfig config = (ResourceConfig) context.getAttribute(attributeName);
+ context.removeAttribute(attributeName);
+ return config;
+ }
+
+ /**
+ * Extract context params from {@link ServletContext}.
+ *
+ * @param servletContext actual servlet context.
+ * @return map representing current context parameters.
+ */
+ public static Map<String, Object> getContextParams(final ServletContext servletContext) {
+ final Map<String, Object> props = new HashMap<>();
+ final Enumeration names = servletContext.getAttributeNames();
+ while (names.hasMoreElements()) {
+ final String name = (String) names.nextElement();
+ props.put(name, servletContext.getAttribute(name));
+ }
+ return props;
+ }
+
+ /**
+ * Prevents instantiation.
+ */
+ private Utils() {
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java
new file mode 100644
index 0000000..6f5d32e
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 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 internal Servlet API.
+ */
+package org.glassfish.jersey.servlet.internal;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java
new file mode 100644
index 0000000..f9fa19b
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.internal.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+
+/**
+ * Implementations could provide their own {@link HttpServletRequest} and {@link HttpServletResponse}
+ * binding implementation in HK2 locator and also an implementation of {@link RequestScopedInitializer}
+ * that is used to set actual request/response references in injection manager within each request.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.21
+ */
+public interface ExtendedServletContainerProvider extends ServletContainerProvider {
+
+ /**
+ * Give me a {@link RequestScopedInitializerProvider} instance, that will be utilized
+ * at runtime to set the actual HTTP Servlet request and response.
+ *
+ * The provider returned will be used at runtime for every and each incoming request
+ * so that the actual request/response instances could be made accessible
+ * from Jersey injection manager.
+ *
+ * @return request scoped initializer provider.
+ */
+ public RequestScopedInitializerProvider getRequestScopedInitializerProvider();
+
+ /**
+ * Used by Jersey runtime to tell if the extension covers HTTP Servlet request response
+ * handling with respect to underlying injection manager.
+ *
+ * Return {@code true}, if your implementation configures HK2 bindings
+ * for {@link HttpServletRequest} and {@link HttpServletResponse}
+ * in {@link #configure(ResourceConfig)} method
+ * and also provides a {@link RequestScopedInitializer} implementation
+ * via {@link #getRequestScopedInitializerProvider()}.
+ *
+ * @return {@code true} if the extension fully covers HTTP request/response handling.
+ */
+ public boolean bindsServletRequestResponse();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java
new file mode 100644
index 0000000..d4147d8
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.internal.spi;
+
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Basic {@link ExtendedServletContainerProvider} that provides
+ * dummy no-op method implementation. It should be convenient to extend if you only need to implement
+ * a subset of the original SPI methods.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class NoOpServletContainerProvider implements ExtendedServletContainerProvider {
+
+ public final Type HTTP_SERVLET_REQUEST_TYPE = (new GenericType<Ref<HttpServletRequest>>() { }).getType();
+ public final Type HTTP_SERVLET_RESPONSE_TYPE = (new GenericType<Ref<HttpServletResponse>>() { }).getType();
+
+ @Override
+ public void preInit(final ServletContext servletContext, final Set<Class<?>> classes) throws ServletException {
+ // no-op
+ }
+
+ @Override
+ public void postInit(
+ final ServletContext servletContext, final Set<Class<?>> classes, final Set<String> servletNames) {
+ // no-op
+ }
+
+ @Override
+ public void onRegister(
+ final ServletContext servletContext, final Set<String> servletNames) throws ServletException {
+ // no-op
+ }
+
+ @Override
+ public void configure(final ResourceConfig resourceConfig) throws ServletException {
+ // no-op
+ }
+
+ @Override
+ public boolean bindsServletRequestResponse() {
+ return false;
+ }
+
+ @Override
+ public RequestScopedInitializerProvider getRequestScopedInitializerProvider() {
+ return null;
+ }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java
new file mode 100644
index 0000000..49049f2
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.internal.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Provide access to the actual servlet request/response.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @see {@link RequestScopedInitializerProvider}
+ */
+public interface RequestContextProvider {
+
+ /**
+ * Get me the actual HTTP Servlet request.
+ *
+ * @return actual HTTP Servlet request.
+ */
+ public HttpServletRequest getHttpServletRequest();
+
+ /**
+ * Get me the actual HTTP Servlet response.
+ *
+ * @return actual HTTP Servlet response.
+ */
+ public HttpServletResponse getHttpServletResponse();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java
new file mode 100644
index 0000000..ea18626
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.internal.spi;
+
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+
+/**
+ * Produces {@link RequestScopedInitializer}
+ * based on provided {@link RequestContextProvider}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface RequestScopedInitializerProvider {
+
+ /**
+ * Give me a request scope initializer that could be utilized
+ * to set the actual Servlet request data in injection manager.
+ *
+ * @param context of the actual request.
+ * @return initializer to be invoked at the start of request processing.
+ */
+ public RequestScopedInitializer get(RequestContextProvider context);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java
new file mode 100644
index 0000000..2e9b697
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013, 2018 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.servlet.internal.spi;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+import org.glassfish.jersey.servlet.ServletContainer;
+
+/**
+ * This is internal Jersey SPI to hook to Jersey servlet initialization process driven by
+ * {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}.
+ * The provider implementation class is registered via {@code META-INF/services}.
+ *
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @since 2.4.1
+ */
+public interface ServletContainerProvider {
+
+ /**
+ * Do your pre-initialization job before Jersey starts its servlet initialization.
+ *
+ * It is allowed to configure {@link ServletContext} or add/remove servlet registrations.
+ * Parameter {@code servletNames} contains list of names of currently registered Jersey servlets.
+ *
+ * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+ * @param classes the mutable Set of application classes that extend {@link javax.ws.rs.core.Application},
+ * implement, or have been annotated with the class types {@link javax.ws.rs.Path},
+ * {@link javax.ws.rs.ext.Provider} or {@link javax.ws.rs.ApplicationPath}.
+ * May be empty, never {@code null}.
+ * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+ * is interrupted.
+ */
+ public void preInit(ServletContext servletContext, Set<Class<?>> classes) throws ServletException;
+
+ /**
+ * Do your post-initialization job after Jersey finished its servlet initialization.
+ *
+ * It is allowed to configure {@link ServletContext} or add/remove servlet registrations.
+ * Parameter {@code servletNames} contains list of names of currently registered Jersey servlets.
+ *
+ * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+ * @param classes the mutable Set of application classes that extend {@link javax.ws.rs.core.Application},
+ * implement, or have been annotated with the class types {@link javax.ws.rs.Path},
+ * {@link javax.ws.rs.ext.Provider} or {@link javax.ws.rs.ApplicationPath}.
+ * May be empty, never {@code null}.
+ * @param servletNames the Immutable set of Jersey's ServletContainer names. May be empty, never {@code null}.
+ * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+ * is interrupted.
+ */
+ public void postInit(ServletContext servletContext, Set<Class<?>> classes, final Set<String> servletNames)
+ throws ServletException;
+
+ /**
+ * Notifies the provider about all registered Jersey servlets by its names.
+ *
+ * It is allowed to configure {@link ServletContext}. Do not add/remove any servlet registrations here.
+ *
+ * Parameter {@code servletNames} contains list of names of registered Jersey servlets.
+ * Currently it is {@link ServletContainer} or
+ * {@code org.glassfish.jersey.servlet.portability.PortableServletContainer} servlets.
+ *
+ * It does not matter servlet container is configured in {@code web.xml},
+ * by {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}
+ * or by customer direct Servlet API calls.
+ *
+ * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+ * @param servletNames the Immutable set of Jersey's ServletContainer names. May be empty, never {@code null}.
+ * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+ * is interrupted.
+ */
+ public void onRegister(ServletContext servletContext, final Set<String> servletNames) throws ServletException;
+
+ /**
+ * This method is called for each {@link ServletContainer} instance initialization,
+ * i.e. during {@link org.glassfish.jersey.servlet.WebComponent} initialization.
+ *
+ * The method is also called during {@link ServletContainer#reload()} or
+ * {@link ServletContainer#reload(ResourceConfig)} methods invocation.
+ *
+ * It does not matter servlet container is configured in {@code web.xml},
+ * by {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}
+ * or by customer direct Servlet API calls.
+ *
+ * @param resourceConfig Jersey application configuration.
+ * @throws ServletException if an error has occurred. {@code org.glassfish.jersey.servlet.WebComponent} construction
+ * is interrupted.
+ */
+ public void configure(ResourceConfig resourceConfig) throws ServletException;
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java
new file mode 100644
index 0000000..e0875ca
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 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 internal Servlet SPI.
+ */
+package org.glassfish.jersey.servlet.internal.spi;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java
new file mode 100644
index 0000000..8e99e29
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 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 generic Servlet container integration classes.
+ */
+package org.glassfish.jersey.servlet;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java
new file mode 100644
index 0000000..99a02a8
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet.spi;
+
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+/**
+ * Utilized by the Servlet container response writer to deal with the container async features.
+ * Individual instances are created by {@link AsyncContextDelegateProvider}.
+ *
+ * @see AsyncContextDelegateProvider
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface AsyncContextDelegate {
+
+ /**
+ * Invoked by the superior {@link ContainerResponseWriter} responsible for writing the response when processing is to be
+ * suspended. An implementation can throw an {@link UnsupportedOperationException} if suspend is not supported (the default
+ * behavior).
+ *
+ * @see ContainerResponseWriter#suspend(long, java.util.concurrent.TimeUnit, org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler)
+ * @throws IllegalStateException if underlying {@link javax.servlet.ServletRequest servlet request} throws an exception.
+ */
+ public void suspend() throws IllegalStateException;
+
+ /**
+ * Invoked upon a response writing completion when the response write is either committed or canceled.
+ */
+ public void complete();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java
new file mode 100644
index 0000000..8503ff1
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Factory to create {@link AsyncContextDelegate} to deal with asynchronous
+ * features added in Servlet version 3.0.
+ * If no such a factory is registered via the {@code META-INF/services} mechanism
+ * a default factory for Servlet versions prior to 3.0 will be utilized with no async support.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface AsyncContextDelegateProvider {
+
+ /**
+ * Factory method to create instances of Servlet container response writer extension,
+ * {@link AsyncContextDelegate}, for response processing.
+ *
+ * @param request original request.
+ * @param response original response.
+ * @return an instance to be used throughout a single response write processing.
+ */
+ public AsyncContextDelegate createDelegate(final HttpServletRequest request, final HttpServletResponse response);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java
new file mode 100644
index 0000000..a68b5ea
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.spi;
+
+import javax.servlet.FilterConfig;
+import java.util.List;
+
+/**
+ * Provides an access to context path from the filter configuration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public interface FilterUrlMappingsProvider {
+
+ /**
+ * Return configured context path from the filter configuration.
+ *
+ * @param filterConfig the {@link FilterConfig} object
+ * @returns the {@code List} of url-patterns
+ */
+ List<String> getFilterUrlMappings(final FilterConfig filterConfig);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java
new file mode 100644
index 0000000..c8a1825
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 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
+ */
+
+/**
+ * SPI for Jersey generic Servlet container support.
+ */
+package org.glassfish.jersey.servlet.spi;
diff --git a/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties b/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties
new file mode 100644
index 0000000..4250a68
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2012, 2018 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
+#
+
+async.processing.not.supported=Asynchronous processing not supported on Servlet 2.x container.
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+form.param.consumed=A servlet request to the URI {0} contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
+init.param.regex.syntax.invalid=The syntax is invalid for the regular expression "{0}" associated with the initialization parameter "{1}".
+# {0} - name (e.g. 'BookmarkPU')
+persistence.unit.not.configured=Persistence unit "{0}" is not configured as a servlet parameter in web.xml.
+# {0} - class name
+no.thread.local.value=No thread local value in scope for proxy of {0}.
+resource.config.parent.class.invalid=Resource configuration class {0} is not a subclass of {1}.
+resource.config.unable.to.load=Resource configuration class {0} could not be loaded.
+servlet.path.mismatch=The servlet path {0} does not start with the filter context path {1}.
+servlet.request.suspend.failed=Attempt to put servlet request into asynchronous mode has failed. Please check your servlet configuration \
+ - all Servlet instances and Servlet filters involved in the request processing must explicitly declare support for asynchronous request processing.
+header.value.read.failed=Attempt to read the header value failed.
+filter.context.path.missing=The root of the app was not properly defined. Either use a Servlet 3.x container or add \
+ an init-param 'jersey.config.servlet.filter.contextPath' to the filter configuration. Due to Servlet 2.x API, Jersey cannot \
+ determine the request base URI solely from the ServletContext. The application will most likely not work.
diff --git a/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java
new file mode 100644
index 0000000..b4476fc
--- /dev/null
+++ b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2014, 2018 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.servlet.internal;
+
+import java.lang.reflect.Proxy;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Michal Gajdos
+ */
+public class ThreadLocalInvokerTest {
+
+ public static class CheckedException extends Exception {
+
+ }
+
+ public static interface X {
+
+ public String checked() throws CheckedException;
+
+ public String runtime();
+ }
+
+ @Test
+ public void testIllegalState() {
+ final ThreadLocalInvoker<X> tli = new ThreadLocalInvoker<>();
+
+ final X x = (X) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{X.class}, tli);
+
+ boolean caught = false;
+ try {
+ x.checked();
+ } catch (final Exception ex) {
+ caught = true;
+ assertEquals(IllegalStateException.class, ex.getClass());
+ }
+ assertTrue(caught);
+
+ caught = false;
+ try {
+ x.runtime();
+ } catch (final Exception ex) {
+ caught = true;
+ assertEquals(IllegalStateException.class, ex.getClass());
+ }
+ assertTrue(caught);
+ }
+
+ @Test
+ public void testExceptions() {
+ final ThreadLocalInvoker<X> tli = new ThreadLocalInvoker<>();
+
+ final X x = (X) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{X.class}, tli);
+
+ tli.set(new X() {
+ public String checked() throws CheckedException {
+ throw new CheckedException();
+ }
+
+ public String runtime() {
+ throw new RuntimeException();
+ }
+ });
+
+ boolean caught = false;
+ try {
+ x.checked();
+ } catch (final Exception ex) {
+ caught = true;
+ assertEquals(CheckedException.class, ex.getClass());
+ }
+ assertTrue(caught);
+
+ caught = false;
+ try {
+ x.runtime();
+ } catch (final Exception ex) {
+ caught = true;
+ assertEquals(RuntimeException.class, ex.getClass());
+ }
+ assertTrue(caught);
+ }
+}
diff --git a/containers/jersey-servlet/pom.xml b/containers/jersey-servlet/pom.xml
new file mode 100644
index 0000000..a9a6634
--- /dev/null
+++ b/containers/jersey-servlet/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-servlet</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-servlet</name>
+
+ <description>Jersey core Servlet 3.x implementation</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>${servlet3.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- Note: When you're changing these properties change them also in bundles/jax-rs-ri/bundle/pom.xml. -->
+ <Import-Package>
+ javax.servlet.*;version="[3.0,5.0)",
+ javax.annotation.*;version=!,
+ *
+ </Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java
new file mode 100644
index 0000000..25ad283
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet.async;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.servlet.init.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider;
+
+/**
+ * Servlet 3.x container response writer async extension and related extension factory implementation.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class AsyncContextDelegateProviderImpl implements AsyncContextDelegateProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(AsyncContextDelegateProviderImpl.class.getName());
+
+ @Override
+ public final AsyncContextDelegate createDelegate(final HttpServletRequest request, final HttpServletResponse response) {
+ return new ExtensionImpl(request, response);
+ }
+
+ private static final class ExtensionImpl implements AsyncContextDelegate {
+
+ private static final int NEVER_TIMEOUT_VALUE = -1;
+
+ private final HttpServletRequest request;
+ private final HttpServletResponse response;
+ private final AtomicReference<AsyncContext> asyncContextRef;
+ private final AtomicBoolean completed;
+
+ /**
+ * Create a Servlet 3.x {@link AsyncContextDelegate} with given {@code request} and {@code response}.
+ *
+ * @param request request to create {@link AsyncContext} for.
+ * @param response response to create {@link AsyncContext} for.
+ */
+ private ExtensionImpl(final HttpServletRequest request, final HttpServletResponse response) {
+ this.request = request;
+ this.response = response;
+ this.asyncContextRef = new AtomicReference<>();
+ this.completed = new AtomicBoolean(false);
+ }
+
+ @Override
+ public void suspend() throws IllegalStateException {
+ // Suspend only if not completed and not suspended before.
+ if (!completed.get() && asyncContextRef.get() == null) {
+ asyncContextRef.set(getAsyncContext());
+ }
+ }
+
+ private AsyncContext getAsyncContext() {
+ final AsyncContext asyncContext;
+ if (request.isAsyncStarted()) {
+ asyncContext = request.getAsyncContext();
+ try {
+ asyncContext.setTimeout(NEVER_TIMEOUT_VALUE);
+ } catch (IllegalStateException ex) {
+ // Let's hope the time out is set properly, otherwise JAX-RS AsyncResponse time-out support
+ // may not work as expected... At least we can log this at fine level...
+ LOGGER.log(Level.FINE, LocalizationMessages.SERVLET_ASYNC_CONTEXT_ALREADY_STARTED(), ex);
+ }
+ } else {
+ asyncContext = request.startAsync(request, response);
+ // Tell underlying asyncContext to never time out.
+ asyncContext.setTimeout(NEVER_TIMEOUT_VALUE);
+ }
+ return asyncContext;
+ }
+
+ @Override
+ public void complete() {
+ completed.set(true);
+
+ final AsyncContext asyncContext = asyncContextRef.getAndSet(null);
+ if (asyncContext != null) {
+ asyncContext.complete();
+ }
+ }
+ }
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java
new file mode 100644
index 0000000..0e02af9
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 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 servlet container asynchronous support classes.
+ */
+package org.glassfish.jersey.servlet.async;
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java
new file mode 100644
index 0000000..e632f72
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015, 2018 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.servlet.init;
+
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Provide all configured context paths (url mappings) of the application deployed using filter.
+ * <p>
+ * The url patterns are returned without the eventual trailing asterisk.
+ * <p>
+ * The functionality is available in Servlet 3.x environment only, so this
+ * implementation of {@link FilterUrlMappingsProvider} interface is Servlet 3 specific.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class FilterUrlMappingsProviderImpl implements FilterUrlMappingsProvider {
+ @Override
+ public List<String> getFilterUrlMappings(FilterConfig filterConfig) {
+ FilterRegistration filterRegistration =
+ filterConfig.getServletContext().getFilterRegistration(filterConfig.getFilterName());
+
+ Collection<String> urlPatternMappings = filterRegistration.getUrlPatternMappings();
+ List<String> result = new ArrayList<>();
+
+ for (String pattern : urlPatternMappings) {
+ result.add(pattern.endsWith("*") ? pattern.substring(0, pattern.length() - 1) : pattern);
+ }
+
+ return result;
+ }
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java
new file mode 100644
index 0000000..5d58730
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2012, 2018 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.servlet.init;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.ext.Provider;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.annotation.HandlesTypes;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.servlet.ServletProperties;
+import org.glassfish.jersey.servlet.init.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.ServletContainerProviderFactory;
+import org.glassfish.jersey.servlet.internal.Utils;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+
+/*
+ It is RECOMMENDED that implementations support the Servlet 3 framework
+ pluggability mechanism to enable portability between containers and to avail
+ themselves of container-supplied class scanning facilities.
+ When using the pluggability mechanism the following conditions MUST be met:
+
+ - If no Application subclass is present the added servlet MUST be
+ named "javax.ws.rs.core.Application" and all root resource classes and
+ providers packaged in the web application MUST be included in the published
+ JAX-RS application. The application MUST be packaged with a web.xml that
+ specifies a servlet mapping for the added servlet.
+
+ - If an Application subclass is present and there is already a servlet defined
+ that has a servlet initialization parameter named "javax.ws.rs.Application"
+ whose value is the fully qualified name of the Application subclass then no
+ servlet should be added by the JAX-RS implementation's ContainerInitializer
+ since the application is already being handled by an existing servlet.
+
+ - If an application subclass is present that is not being handled by an
+ existing servlet then the servlet added by the ContainerInitializer MUST be
+ named with the fully qualified name of the Application subclass. If the
+ Application subclass is annotated with @PathPrefix and no servlet-mapping
+ exists for the added servlet then a new servlet mapping is added with the
+ value of the @PathPrefix annotation with "/*" appended otherwise the existing
+ mapping is used. If the Application subclass is not annotated with @PathPrefix
+ then the application MUST be packaged with a web.xml that specifies a servlet
+ mapping for the added servlet. It is an error for more than one Application
+ to be deployed at the same effective servlet mapping.
+
+ In either of the latter two cases, if both Application#getClasses and
+ Application#getSingletons return an empty list then all root resource classes
+ and providers packaged in the web application MUST be included in the
+ published JAX-RS application. If either getClasses or getSingletons return a
+ non-empty list then only those classes or singletons returned MUST be included
+ in the published JAX-RS application.
+
+ If not using the Servlet 3 framework pluggability mechanism
+ (e.g. in a pre-Servlet 3.0 container), the servlet-class or filter-class
+ element of the web.xml descriptor SHOULD name the JAX-RS
+ implementation-supplied Servlet or Filter class respectively. The
+ application-supplied subclass of Application SHOULD be identified using an
+ init-param with a param-name of javax.ws.rs.Application.
+ */
+
+/**
+ * {@link ServletContainerInitializer} implementation used for Servlet 3.x deployment.
+ *
+ * @author Paul Sandoz
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@HandlesTypes({ Path.class, Provider.class, Application.class, ApplicationPath.class })
+public final class JerseyServletContainerInitializer implements ServletContainerInitializer {
+
+ private static final Logger LOGGER = Logger.getLogger(JerseyServletContainerInitializer.class.getName());
+
+ @Override
+ public void onStartup(Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
+ final ServletContainerProvider[] allServletContainerProviders =
+ ServletContainerProviderFactory.getAllServletContainerProviders();
+
+ if (classes == null) {
+ classes = Collections.emptySet();
+ }
+ // PRE INIT
+ for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+ servletContainerProvider.preInit(servletContext, classes);
+ }
+ // INIT IMPL
+ onStartupImpl(classes, servletContext);
+ // POST INIT
+ for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+ servletContainerProvider.postInit(servletContext, classes, findJerseyServletNames(servletContext));
+ }
+ // ON REGISTER
+ for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+ servletContainerProvider.onRegister(servletContext, findJerseyServletNames(servletContext));
+ }
+ }
+
+ private void onStartupImpl(final Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
+ // first see if there are any application classes in the web app
+ for (final Class<? extends Application> applicationClass : getApplicationClasses(classes)) {
+ final ServletRegistration servletRegistration = servletContext.getServletRegistration(applicationClass.getName());
+
+ if (servletRegistration != null) {
+ addServletWithExistingRegistration(servletContext, servletRegistration, applicationClass, classes);
+ } else {
+ // Servlet is not registered with app name or the app name is used to register a different servlet
+ // check if some servlet defines the app in init params
+ final List<Registration> srs = getInitParamDeclaredRegistrations(servletContext, applicationClass);
+ if (!srs.isEmpty()) {
+ // app handled by at least one servlet or filter
+ // fix the registrations if needed (i.e. add servlet class)
+ for (final Registration sr : srs) {
+ if (sr instanceof ServletRegistration) {
+ addServletWithExistingRegistration(servletContext, (ServletRegistration) sr,
+ applicationClass, classes);
+ }
+ }
+ } else {
+ // app not handled by any servlet/filter -> add it
+ addServletWithApplication(servletContext, applicationClass, classes);
+ }
+ }
+ }
+
+ // check for javax.ws.rs.core.Application registration
+ addServletWithDefaultConfiguration(servletContext, classes);
+ }
+
+ /**
+ * Returns names of all registered Jersey servlets.
+ *
+ * Servlets are configured in {@code web.xml} or managed via Servlet API.
+ *
+ * @param servletContext the {@link ServletContext} of the web application that is being started
+ * @return list of Jersey servlet names or empty array, never returns {@code null}
+ */
+ private static Set<String> findJerseyServletNames(final ServletContext servletContext) {
+ final Set<String> jerseyServletNames = new HashSet<>();
+
+ for (final ServletRegistration servletRegistration : servletContext.getServletRegistrations().values()) {
+ if (isJerseyServlet(servletRegistration.getClassName())) {
+ jerseyServletNames.add(servletRegistration.getName());
+ }
+ }
+ return Collections.unmodifiableSet(jerseyServletNames);
+ }
+
+ /**
+ * Check if the {@code className} is an implementation of a Jersey Servlet container.
+ *
+ * @return {@code true} if the class is a Jersey servlet container class, {@code false} otherwise.
+ */
+ private static boolean isJerseyServlet(final String className) {
+ return ServletContainer.class.getName().equals(className)
+ || "org.glassfish.jersey.servlet.portability.PortableServletContainer".equals(className);
+ }
+
+ private static List<Registration> getInitParamDeclaredRegistrations(final ServletContext context,
+ final Class<? extends Application> clazz) {
+ final List<Registration> registrations = new ArrayList<>();
+ collectJaxRsRegistrations(context.getServletRegistrations(), registrations, clazz);
+ collectJaxRsRegistrations(context.getFilterRegistrations(), registrations, clazz);
+ return registrations;
+ }
+
+ private static void collectJaxRsRegistrations(final Map<String, ? extends Registration> registrations,
+ final List<Registration> collected, final Class<? extends Application> a) {
+ for (final Registration sr : registrations.values()) {
+ final Map<String, String> ips = sr.getInitParameters();
+ if (ips.containsKey(ServletProperties.JAXRS_APPLICATION_CLASS)) {
+ if (ips.get(ServletProperties.JAXRS_APPLICATION_CLASS).equals(a.getName())) {
+ collected.add(sr);
+ }
+ }
+ }
+ }
+
+ /**
+ * Enhance default servlet (named {@link Application}) configuration.
+ */
+ private static void addServletWithDefaultConfiguration(final ServletContext context,
+ final Set<Class<?>> classes) throws ServletException {
+
+ ServletRegistration registration = context.getServletRegistration(Application.class.getName());
+
+ if (registration != null) {
+ final Set<Class<?>> appClasses = getRootResourceAndProviderClasses(classes);
+ final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(ResourceConfig.class, appClasses)
+ .addProperties(getInitParams(registration))
+ .addProperties(Utils.getContextParams(context));
+
+ if (registration.getClassName() != null) {
+ // class name present - complete servlet registration from container point of view
+ Utils.store(resourceConfig, context, registration.getName());
+ } else {
+ // no class name - no complete servlet registration from container point of view
+ final ServletContainer servlet = new ServletContainer(resourceConfig);
+ registration = context.addServlet(registration.getName(), servlet);
+ ((ServletRegistration.Dynamic) registration).setLoadOnStartup(1);
+
+ if (registration.getMappings().isEmpty()) {
+ // Error
+ LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_NO_MAPPING(registration.getName()));
+ } else {
+ LOGGER.log(Level.CONFIG,
+ LocalizationMessages.JERSEY_APP_REGISTERED_CLASSES(registration.getName(), appClasses));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add new servlet according to {@link Application} subclass with {@link ApplicationPath} annotation or existing
+ * {@code servlet-mapping}.
+ */
+ private static void addServletWithApplication(final ServletContext context,
+ final Class<? extends Application> clazz,
+ final Set<Class<?>> defaultClasses) throws ServletException {
+ final ApplicationPath ap = clazz.getAnnotation(ApplicationPath.class);
+ if (ap != null) {
+ // App is annotated with ApplicationPath
+ final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(clazz, defaultClasses)
+ .addProperties(Utils.getContextParams(context));
+ final ServletContainer s = new ServletContainer(resourceConfig);
+ final ServletRegistration.Dynamic dsr = context.addServlet(clazz.getName(), s);
+ dsr.setAsyncSupported(true);
+ dsr.setLoadOnStartup(1);
+
+ final String mapping = createMappingPath(ap);
+ if (!mappingExists(context, mapping)) {
+ dsr.addMapping(mapping);
+
+ LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_MAPPING(clazz.getName(), mapping));
+ } else {
+ LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_MAPPING_CONFLICT(clazz.getName(), mapping));
+ }
+ }
+ }
+
+ /**
+ * Enhance existing servlet configuration.
+ */
+ private static void addServletWithExistingRegistration(final ServletContext context,
+ ServletRegistration registration,
+ final Class<? extends Application> clazz,
+ final Set<Class<?>> classes) throws ServletException {
+ // create a new servlet container for a given app.
+ final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(clazz, classes)
+ .addProperties(getInitParams(registration))
+ .addProperties(Utils.getContextParams(context));
+
+ if (registration.getClassName() != null) {
+ // class name present - complete servlet registration from container point of view
+ Utils.store(resourceConfig, context, registration.getName());
+ } else {
+ // no class name - no complete servlet registration from container point of view
+ final ServletContainer servlet = new ServletContainer(resourceConfig);
+ final ServletRegistration.Dynamic dynamicRegistration = context.addServlet(clazz.getName(), servlet);
+ dynamicRegistration.setAsyncSupported(true);
+ dynamicRegistration.setLoadOnStartup(1);
+ registration = dynamicRegistration;
+ }
+ if (registration.getMappings().isEmpty()) {
+ final ApplicationPath ap = clazz.getAnnotation(ApplicationPath.class);
+ if (ap != null) {
+ final String mapping = createMappingPath(ap);
+ if (!mappingExists(context, mapping)) {
+ registration.addMapping(mapping);
+
+ LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_MAPPING(clazz.getName(), mapping));
+ } else {
+ LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_MAPPING_CONFLICT(clazz.getName(), mapping));
+ }
+ } else {
+ // Error
+ LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_NO_MAPPING_OR_ANNOTATION(clazz.getName(),
+ ApplicationPath.class.getSimpleName()));
+ }
+ } else {
+ LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_APPLICATION(clazz.getName()));
+ }
+ }
+
+ private static Map<String, Object> getInitParams(final ServletRegistration sr) {
+ final Map<String, Object> initParams = new HashMap<>();
+ for (final Map.Entry<String, String> entry : sr.getInitParameters().entrySet()) {
+ initParams.put(entry.getKey(), entry.getValue());
+ }
+ return initParams;
+ }
+
+ private static boolean mappingExists(final ServletContext sc, final String mapping) {
+ for (final ServletRegistration sr : sc.getServletRegistrations().values()) {
+ for (final String declaredMapping : sr.getMappings()) {
+ if (mapping.equals(declaredMapping)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+
+ private static String createMappingPath(final ApplicationPath ap) {
+ String path = ap.value();
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+
+ if (!path.endsWith("/*")) {
+ if (path.endsWith("/")) {
+ path += "*";
+ } else {
+ path += "/*";
+ }
+ }
+
+ return path;
+ }
+
+ private static Set<Class<? extends Application>> getApplicationClasses(final Set<Class<?>> classes) {
+ final Set<Class<? extends Application>> s = new LinkedHashSet<>();
+ for (final Class<?> c : classes) {
+ if (Application.class != c && Application.class.isAssignableFrom(c)) {
+ s.add(c.asSubclass(Application.class));
+ }
+ }
+
+ return s;
+ }
+
+ private static Set<Class<?>> getRootResourceAndProviderClasses(final Set<Class<?>> classes) {
+ // TODO filter out any classes from the Jersey jars
+ final Set<Class<?>> s = new LinkedHashSet<>();
+ for (final Class<?> c : classes) {
+ if (c.isAnnotationPresent(Path.class) || c.isAnnotationPresent(Provider.class)) {
+ s.add(c);
+ }
+ }
+
+ return s;
+ }
+
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java
new file mode 100644
index 0000000..c19811d
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 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 servlet container initialization classes.
+ */
+package org.glassfish.jersey.servlet.init;
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..4052e74
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider
new file mode 100644
index 0000000..959ec7c
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.async.AsyncContextDelegateProviderImpl
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider
new file mode 100644
index 0000000..6f15689
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.init.FilterUrlMappingsProviderImpl
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties b/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties
new file mode 100644
index 0000000..6f1b074
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2012, 2018 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.app.mapping.conflict=Mapping conflict. A Servlet registration exists with same mapping as the Jersey servlet application, named {0}, at the servlet mapping, {1}.
+jersey.app.no.mapping=The Jersey servlet application, named {0}, has no servlet mapping.
+jersey.app.no.mapping.or.annotation=The Jersey servlet application, named {0}, is not annotated with {1} and has no servlet mapping.
+jersey.app.registered.classes=Registering the Jersey servlet application, named {0}, with the following root resource and provider classes: {1}
+jersey.app.registered.mapping=Registering the Jersey servlet application, named {0}, at the servlet mapping {1}, with the Application class of the same name.
+jersey.app.registered.application=Registering the Jersey servlet application, named {0}, with the Application class of the same name.
+servlet.async.context.already.started=Servlet request has been put into asynchronous mode by an external force. \
+ Proceeding with the existing AsyncContext instance, but cannot guarantee the correct behavior of JAX-RS AsyncResponse time-out support.
diff --git a/containers/jetty-http/pom.xml b/containers/jetty-http/pom.xml
new file mode 100644
index 0000000..eb3d0b8
--- /dev/null
+++ b/containers/jetty-http/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-jetty-http</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-jetty-http</name>
+
+ <description>Jetty Http Container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-continuation</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>testsSkipJdk6</id>
+ <activation>
+ <jdk>1.6</jdk>
+ </activation>
+ <properties>
+ <skip.tests>true</skip.tests>
+ </properties>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java
new file mode 100644
index 0000000..fa93775
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (c) 2013, 2018 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.jetty;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.process.internal.RequestScoped;
+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.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+/**
+ * Jersey {@code Container} implementation based on Jetty {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainer extends AbstractHandler implements Container {
+
+ private static final ExtendedLogger LOGGER =
+ new ExtendedLogger(Logger.getLogger(JettyHttpContainer.class.getName()), Level.FINEST);
+
+ private static final Type REQUEST_TYPE = (new GenericType<Ref<Request>>() {}).getType();
+ private static final Type RESPONSE_TYPE = (new GenericType<Ref<Response>>() {}).getType();
+
+ private static final int INTERNAL_SERVER_ERROR = javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
+
+ /**
+ * Cached value of configuration property
+ * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+ * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+ */
+ private boolean configSetStatusOverSendError;
+
+ /**
+ * Referencing factory for Jetty request.
+ */
+ private static class JettyRequestReferencingFactory extends ReferencingFactory<Request> {
+ @Inject
+ public JettyRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * Referencing factory for Jetty response.
+ */
+ private static class JettyResponseReferencingFactory extends ReferencingFactory<Response> {
+ @Inject
+ public JettyResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * An internal binder to enable Jetty HTTP container specific types injection.
+ * This binder allows to inject underlying Jetty HTTP request and response instances.
+ * Note that since Jetty {@code Request} class is not proxiable as it does not expose an empty constructor,
+ * the injection of Jetty request instance into singleton JAX-RS and Jersey providers is only supported via
+ * {@link javax.inject.Provider injection provider}.
+ */
+ private static class JettyBinder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bindFactory(JettyRequestReferencingFactory.class).to(Request.class)
+ .proxy(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Request>referenceFactory()).to(new GenericType<Ref<Request>>() {})
+ .in(RequestScoped.class);
+
+ bindFactory(JettyResponseReferencingFactory.class).to(Response.class)
+ .proxy(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Response>referenceFactory()).to(new GenericType<Ref<Response>>() {})
+ .in(RequestScoped.class);
+ }
+ }
+
+ private volatile ApplicationHandler appHandler;
+
+ @Override
+ public void handle(final String target, final Request request, final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse) throws IOException, ServletException {
+
+ final Response response = request.getResponse();
+ final ResponseWriter responseWriter = new ResponseWriter(request, response, configSetStatusOverSendError);
+ final URI baseUri = getBaseUri(request);
+ final URI requestUri = getRequestUri(request, baseUri);
+ try {
+ final ContainerRequest requestContext = new ContainerRequest(
+ baseUri,
+ requestUri,
+ request.getMethod(),
+ getSecurityContext(request),
+ new MapPropertiesDelegate());
+ requestContext.setEntityStream(request.getInputStream());
+ final Enumeration<String> headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ final String headerName = headerNames.nextElement();
+ String headerValue = request.getHeader(headerName);
+ requestContext.headers(headerName, headerValue == null ? "" : headerValue);
+ }
+ requestContext.setWriter(responseWriter);
+ requestContext.setRequestScopedInitializer(injectionManager -> {
+ injectionManager.<Ref<Request>>getInstance(REQUEST_TYPE).set(request);
+ injectionManager.<Ref<Response>>getInstance(RESPONSE_TYPE).set(response);
+ });
+
+ // Mark the request as handled before generating the body of the response
+ request.setHandled(true);
+ appHandler.handle(requestContext);
+ } catch (final Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+
+ private URI getRequestUri(final Request request, final URI baseUri) {
+ try {
+ final String serverAddress = getServerAddress(baseUri);
+ String uri = request.getRequestURI();
+
+ final String queryString = request.getQueryString();
+ if (queryString != null) {
+ uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+ }
+
+ return new URI(serverAddress + uri);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getServerAddress(URI baseUri) {
+ String serverAddress = baseUri.toString();
+ if (serverAddress.charAt(serverAddress.length() - 1) == '/') {
+ return serverAddress.substring(0, serverAddress.length() - 1);
+ }
+ return serverAddress;
+ }
+
+ private SecurityContext getSecurityContext(final Request request) {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return request.isUserInRole(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.isSecure();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return request.getUserPrincipal();
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return request.getAuthType();
+ }
+ };
+ }
+
+
+ private URI getBaseUri(final Request request) {
+ try {
+ return new URI(request.getScheme(), null, request.getServerName(),
+ request.getServerPort(), getBasePath(request), null, null);
+ } catch (final URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getBasePath(final Request request) {
+ final String contextPath = request.getContextPath();
+
+ if (contextPath == null || contextPath.isEmpty()) {
+ return "/";
+ } else if (contextPath.charAt(contextPath.length() - 1) != '/') {
+ return contextPath + "/";
+ } else {
+ return contextPath;
+ }
+ }
+
+ private static final class ResponseWriter implements ContainerResponseWriter {
+
+ private final Response response;
+ private final Continuation continuation;
+ private final boolean configSetStatusOverSendError;
+
+ ResponseWriter(final Request request, final Response response, final boolean configSetStatusOverSendError) {
+ this.response = response;
+ this.continuation = ContinuationSupport.getContinuation(request);
+ this.configSetStatusOverSendError = configSetStatusOverSendError;
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse context)
+ throws ContainerException {
+
+ final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+
+ final int code = statusInfo.getStatusCode();
+ final String reason = statusInfo.getReasonPhrase() == null
+ ? HttpStatus.getMessage(code) : statusInfo.getReasonPhrase();
+
+ response.setStatusWithReason(code, reason);
+
+ if (contentLength != -1 && contentLength < Integer.MAX_VALUE) {
+ response.setContentLength((int) contentLength);
+ }
+ for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+ for (final String value : e.getValue()) {
+ response.addHeader(e.getKey(), value);
+ }
+ }
+
+ try {
+ return response.getOutputStream();
+ } catch (final IOException ioe) {
+ throw new ContainerException("Error during writing out the response headers.", ioe);
+ }
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+ try {
+ if (timeOut > 0) {
+ final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeOut, timeUnit);
+ continuation.setTimeout(timeoutMillis);
+ }
+ continuation.addContinuationListener(new ContinuationListener() {
+ @Override
+ public void onComplete(final Continuation continuation) {
+ }
+
+ @Override
+ public void onTimeout(final Continuation continuation) {
+ if (timeoutHandler != null) {
+ timeoutHandler.onTimeout(ResponseWriter.this);
+ }
+ }
+ });
+ continuation.suspend(response);
+ return true;
+ } catch (final Exception ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+ if (timeOut > 0) {
+ final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeOut, timeUnit);
+ continuation.setTimeout(timeoutMillis);
+ }
+ }
+
+ @Override
+ public void commit() {
+ try {
+ response.closeOutput();
+ } catch (final IOException e) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.UNABLE_TO_CLOSE_RESPONSE(), e);
+ } finally {
+ if (continuation.isSuspended()) {
+ continuation.complete();
+ }
+ LOGGER.log(Level.FINEST, "commit() called");
+ }
+ }
+
+ @Override
+ public void failure(final Throwable error) {
+ try {
+ if (!response.isCommitted()) {
+ try {
+ if (configSetStatusOverSendError) {
+ response.reset();
+ //noinspection deprecation
+ response.setStatus(INTERNAL_SERVER_ERROR, "Request failed.");
+ } else {
+ response.sendError(INTERNAL_SERVER_ERROR, "Request failed.");
+ }
+ } catch (final IllegalStateException ex) {
+ // a race condition externally committing the response can still occur...
+ LOGGER.log(Level.FINER, "Unable to reset failed response.", ex);
+ } catch (final IOException ex) {
+ throw new ContainerException(LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(INTERNAL_SERVER_ERROR,
+ "Request failed."), ex);
+ }
+ }
+ } finally {
+ LOGGER.log(Level.FINEST, "failure(...) called");
+ commit();
+ rethrow(error);
+ }
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return false;
+ }
+
+ /**
+ * Rethrow the original exception as required by JAX-RS, 3.3.4.
+ *
+ * @param error throwable to be re-thrown
+ */
+ private void rethrow(final Throwable error) {
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration.register(new JettyBinder()));
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ cacheConfigSetStatusOverSendError();
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ /**
+ * Inform this container that the server has been started.
+ * This method must be implicitly called after the server containing this container is started.
+ *
+ * @throws java.lang.Exception if a problem occurred during server startup.
+ */
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ appHandler.onStartup(this);
+ }
+
+ /**
+ * Inform this container that the server is being stopped.
+ * This method must be implicitly called before the server containing this container is stopped.
+ *
+ * @throws java.lang.Exception if a problem occurred during server shutdown.
+ */
+ @Override
+ public void doStop() throws Exception {
+ super.doStop();
+ appHandler.onShutdown(this);
+ appHandler = null;
+ }
+
+ /**
+ * Create a new Jetty HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ */
+ JettyHttpContainer(final Application application, final Object parentContext) {
+ this.appHandler = new ApplicationHandler(application, new JettyBinder(), parentContext);
+ }
+
+ /**
+ * Create a new Jetty HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container.
+ */
+ JettyHttpContainer(final Application application) {
+ this.appHandler = new ApplicationHandler(application, new JettyBinder());
+
+ cacheConfigSetStatusOverSendError();
+ }
+
+ /**
+ * The method reads and caches value of configuration property
+ * {@link ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR} for future purposes.
+ */
+ private void cacheConfigSetStatusOverSendError() {
+ this.configSetStatusOverSendError = ServerProperties.getValue(getConfiguration().getProperties(),
+ ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+ }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
new file mode 100644
index 0000000..8155ea7
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2013, 2018 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.jetty;
+
+import java.net.URI;
+import java.util.concurrent.ThreadFactory;
+
+import javax.ws.rs.ProcessingException;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * Factory for creating and starting Jetty server handlers. This returns
+ * a handle to the started server as {@link Server} instances, which allows
+ * the server to be stopped by invoking the {@link org.eclipse.jetty.server.Server#stop()} method.
+ * <p/>
+ * To start the server in HTTPS mode an {@link SslContextFactory} can be provided.
+ * This will be used to decrypt and encrypt information sent over the
+ * connected TCP socket channel.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainerFactory {
+
+ private JettyHttpContainerFactory() {
+ }
+
+ /**
+ * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createServer(final URI uri) throws ProcessingException {
+ return createServer(uri, null, null, true);
+ }
+
+ /**
+ * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+ * segment will be used as context path, the rest will be ignored.
+ * @param start if set to false, server will not get started, which allows to configure the underlying transport
+ * layer, see above for details.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createServer(final URI uri, final boolean start) throws ProcessingException {
+ return createServer(uri, null, null, start);
+ }
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ * <p/>
+ * This implementation defers to the
+ * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+ * for creating an Container that manages the root resources.
+ *
+ * @param uri the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createServer(final URI uri, final ResourceConfig config)
+ throws ProcessingException {
+
+ final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config);
+ return createServer(uri, null, container, true);
+ }
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ * <p/>
+ * This implementation defers to the
+ * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+ * for creating an Container that manages the root resources.
+ *
+ * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be
+ * used as context path, the rest will be ignored.
+ * @param configuration web application configuration.
+ * @param start if set to false, server will not get started, which allows to configure the underlying
+ * transport layer, see above for details.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createServer(final URI uri, final ResourceConfig configuration, final boolean start)
+ throws ProcessingException {
+ return createServer(uri, null, ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start);
+ }
+
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ *
+ * @param uri the URI to create the http server. The URI scheme must be
+ * equal to "https". The URI user information and host
+ * are ignored If the URI port is not present then port 143 will be
+ * used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @param start if set to false, server will not get started, this allows end users to set
+ * additional properties on the underlying listener.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ * @see JettyHttpContainer
+ * @since 2.12
+ */
+ public static Server createServer(final URI uri, final ResourceConfig config, final boolean start,
+ final Object parentContext) {
+ return createServer(uri, null, new JettyHttpContainer(config, parentContext), start);
+ }
+
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ *
+ * @param uri the URI to create the http server. The URI scheme must be
+ * equal to "https". The URI user information and host
+ * are ignored If the URI port is not present then port 143 will be
+ * used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ * @see JettyHttpContainer
+ * @since 2.12
+ */
+ public static Server createServer(final URI uri, final ResourceConfig config, final Object parentContext) {
+ return createServer(uri, null, new JettyHttpContainer(config, parentContext), true);
+ }
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes declared by the
+ * resource configuration.
+ * <p/>
+ * This implementation defers to the
+ * {@link ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+ * for creating an Container that manages the root resources.
+ *
+ * @param uri 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 sslContextFactory this is the SSL context factory used to configure SSL connector
+ * @param config the resource configuration.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ */
+ public static Server createServer(final URI uri, final SslContextFactory sslContextFactory, final ResourceConfig config)
+ throws ProcessingException {
+ final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config);
+ return createServer(uri, sslContextFactory, container, true);
+ }
+
+ /**
+ * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+ * in turn manages all root resource and provider classes found by searching the
+ * classes referenced in the java classpath.
+ *
+ * @param uri 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 sslContextFactory this is the SSL context factory used to configure SSL connector
+ * @param handler the container that handles all HTTP requests
+ * @param start if set to false, server will not get started, this allows end users to set
+ * additional properties on the underlying listener.
+ * @return newly created {@link Server}.
+ *
+ * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance.
+ * @throws IllegalArgumentException if {@code uri} is {@code null}.
+ * @see JettyHttpContainer
+ */
+ public static Server createServer(final URI uri,
+ final SslContextFactory sslContextFactory,
+ final JettyHttpContainer handler,
+ final boolean start) {
+ if (uri == null) {
+ throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
+ }
+ final String scheme = uri.getScheme();
+ int defaultPort = Container.DEFAULT_HTTP_PORT;
+
+ if (sslContextFactory == null) {
+ if (!"http".equalsIgnoreCase(scheme)) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP());
+ }
+ } else {
+ if (!"https".equalsIgnoreCase(scheme)) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS());
+ }
+ defaultPort = Container.DEFAULT_HTTPS_PORT;
+ }
+ final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort();
+
+ final Server server = new Server(new JettyConnectorThreadPool());
+ final HttpConfiguration config = new HttpConfiguration();
+ if (sslContextFactory != null) {
+ config.setSecureScheme("https");
+ config.setSecurePort(port);
+ config.addCustomizer(new SecureRequestCustomizer());
+
+ final ServerConnector https = new ServerConnector(server,
+ new SslConnectionFactory(sslContextFactory, "http/1.1"),
+ new HttpConnectionFactory(config));
+ https.setPort(port);
+ server.setConnectors(new Connector[]{https});
+
+ } else {
+ final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(config));
+ http.setPort(port);
+ server.setConnectors(new Connector[]{http});
+ }
+ if (handler != null) {
+ server.setHandler(handler);
+ }
+
+ if (start) {
+ try {
+ // Start the server.
+ server.start();
+ } catch (final Exception e) {
+ throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e);
+ }
+ }
+ return server;
+ }
+
+ private static final class JettyConnectorThreadPool extends QueuedThreadPool {
+ private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("jetty-http-server-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build();
+
+ @Override
+ protected Thread newThread(Runnable runnable) {
+ return threadFactory.newThread(runnable);
+ }
+ }
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
new file mode 100644
index 0000000..15afcbf
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013, 2018 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.jetty;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.eclipse.jetty.server.Handler;
+
+/**
+ * Container provider for containers based on Jetty Server {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(final Class<T> type, final Application application) throws ProcessingException {
+ if (Handler.class == type || JettyHttpContainer.class == type) {
+ return type.cast(new JettyHttpContainer(application));
+ }
+ return null;
+ }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java
new file mode 100644
index 0000000..4ea14bf
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 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 Jetty container classes.
+ */
+package org.glassfish.jersey.jetty;
diff --git a/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..1496edb
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.JettyHttpContainerProvider
\ No newline at end of file
diff --git a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties
new file mode 100644
index 0000000..b1528f8
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2013, 2018 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
+#
+
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+error.when.creating.server=Exception thrown when trying to create jetty server.
+unable.to.close.response=Unable to close response output.
+uri.cannot.be.null=The URI must not be null.
+wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL.
+wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL.
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
new file mode 100644
index 0000000..dcfe4b7
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, 2018 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.jetty;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.junit.After;
+
+/**
+ * Abstract Jetty Server unit tester.
+ *
+ * @author Paul Sandoz
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Miroslav Fuksa
+ */
+public abstract class AbstractJettyServerTester {
+
+ private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName());
+
+ public static final String CONTEXT = "";
+ private static final int DEFAULT_PORT = 9998;
+
+ /**
+ * Get the port to be used for test application deployments.
+ *
+ * @return The HTTP port of the URI
+ */
+ protected 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 not positive.");
+ }
+ return i;
+ } catch (NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid positive integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+ return DEFAULT_PORT;
+ }
+
+ private volatile Server server;
+
+ public UriBuilder getUri() {
+ return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+ }
+
+ public void startServer(Class... resources) {
+ ResourceConfig config = new ResourceConfig(resources);
+ 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);
+ }
+
+ 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);
+ }
+
+ public URI getBaseUri() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+ }
+
+ public void stopServer() {
+ try {
+ server.stop();
+ server = null;
+ LOGGER.log(Level.INFO, "Jetty-http server stopped.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ stopServer();
+ }
+ }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java
new file mode 100644
index 0000000..b4d6b81
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2013, 2018 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.jetty;
+
+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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.container.TimeoutHandler;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Michal Gajdos
+ */
+public class AsyncTest extends AbstractJettyServerTester {
+
+ @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;
+
+ @Before
+ public void setUp() throws Exception {
+ startServer(AsyncResource.class);
+ client = ClientBuilder.newClient();
+ }
+
+ @Override
+ @After
+ public void tearDown() {
+ super.tearDown();
+ client = null;
+ }
+
+ @Test
+ public void testAsyncGet() throws ExecutionException, InterruptedException {
+ final Future<Response> 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
+ public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException {
+ final Future<Response> 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/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java
new file mode 100644
index 0000000..e934e6e
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010, 2018 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.jetty;
+
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractJettyServerTester {
+ @Path("{status}")
+ public static class ExceptionResource {
+ @GET
+ public String get(@PathParam("status") int status) {
+ throw new WebApplicationException(status);
+ }
+
+ }
+
+ @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/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java
new file mode 100644
index 0000000..1377b88
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2010, 2018 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.jetty;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractJettyServerTester {
+
+ @Path("/one")
+ public static class One {
+ @GET
+ public String get() {
+ return "one";
+ }
+ }
+
+ @Path("/two")
+ public static class Two {
+ @GET
+ public String get() {
+ return "two";
+ }
+ }
+
+ public static class Reloader extends AbstractContainerLifecycleListener {
+ Container container;
+
+ public void reload(ResourceConfig newConfig) {
+ container.reload(newConfig);
+ }
+
+ public void reload() {
+ container.reload();
+ }
+
+ @Override
+ public void onStartup(Container container) {
+ this.container = container;
+ }
+
+ }
+
+ @Test
+ public void testReload() {
+ final ResourceConfig rc = new ResourceConfig(One.class);
+
+ Reloader reloader = new Reloader();
+ rc.registerInstances(reloader);
+
+ startServer(rc);
+
+ Client client = ClientBuilder.newClient();
+ WebTarget r = client.target(getUri().path("/").build());
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+ // add Two resource
+ reloader.reload(new ResourceConfig(One.class, Two.class));
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals("two", r.path("two").request().get(String.class));
+ }
+
+ static class StartStopListener extends AbstractContainerLifecycleListener {
+ volatile boolean started;
+ volatile boolean stopped;
+
+ @Override
+ public void onStartup(Container container) {
+ started = true;
+ }
+
+ @Override
+ public void onShutdown(Container container) {
+ stopped = true;
+ }
+ }
+
+ @Test
+ public void testStartupShutdownHooks() {
+ final StartStopListener listener = new StartStopListener();
+
+ startServer(new ResourceConfig(One.class).register(listener));
+
+ Client client = ClientBuilder.newClient();
+ WebTarget r = client.target(getUri().path("/").build());
+
+ assertThat(r.path("one").request().get(String.class), equalTo("one"));
+ assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+ stopServer();
+
+ assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+ assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+ }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java
new file mode 100644
index 0000000..8866c33
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 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.jetty;
+
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OptionsTest extends AbstractJettyServerTester {
+
+ @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/jetty-servlet/pom.xml b/containers/jetty-servlet/pom.xml
new file mode 100644
index 0000000..8ddf7b4
--- /dev/null
+++ b/containers/jetty-servlet/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-jetty-servlet</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-jetty-servlet</name>
+
+ <description>Jetty Servlet Container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-jetty-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-webapp</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.servlet.*;version="[2.4,5.0)",
+ *
+ </Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java
new file mode 100644
index 0000000..dd585ce
--- /dev/null
+++ b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2013, 2018 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.jetty.servlet;
+
+import java.net.URI;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.uri.UriComponent;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+/**
+ * Factory for creating and starting Jetty {@link Server} instances
+ * for deploying a Servlet.
+ * <p/>
+ * The default deployed server is an instance of {@link ServletContainer}.
+ * <p/>
+ * If no initialization parameters are declared (or is null) then root
+ * resource and provider classes will be found by searching the classes
+ * referenced in the java classpath.
+ *
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ */
+public final class JettyWebContainerFactory {
+
+ private JettyWebContainerFactory() {
+ }
+
+ /**
+ * Create a {@link Server} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(String u)
+ throws Exception {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u));
+ }
+
+ /**
+ * Create a {@link Server} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(String u, Map<String, String> initParams)
+ throws Exception {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), initParams);
+ }
+
+ /**
+ * Create a {@link Server} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(URI u)
+ throws Exception {
+ return create(u, ServletContainer.class);
+ }
+
+ /**
+ * Create a {@link Server} that registers the {@link ServletContainer}.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(URI u, Map<String, String> initParams)
+ throws Exception {
+ return create(u, ServletContainer.class, initParams);
+ }
+
+ /**
+ * Create a {@link Server} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(String u, Class<? extends Servlet> c)
+ throws Exception {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), c);
+ }
+
+ /**
+ * Create a {@link Server} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(String u, Class<? extends Servlet> c,
+ Map<String, String> initParams)
+ throws Exception {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ return create(URI.create(u), c, initParams);
+ }
+
+ /**
+ * Create a {@link Server} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(URI u, Class<? extends Servlet> c)
+ throws Exception {
+ return create(u, c, null);
+ }
+
+ /**
+ * Create a {@link Server} that registers the declared
+ * servlet class.
+ *
+ * @param u the URI to create the http server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param c the servlet class.
+ * @param initParams the servlet initialization parameters.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(URI u, Class<? extends Servlet> c, Map<String, String> initParams)
+ throws Exception {
+ return create(u, c, null, initParams, null);
+ }
+
+ private static Server create(URI u, Class<? extends Servlet> c, Servlet servlet,
+ Map<String, String> initParams, Map<String, String> contextInitParams)
+ throws Exception {
+ if (u == null) {
+ throw new IllegalArgumentException("The URI must not be null");
+ }
+
+ String path = u.getPath();
+ if (path == null) {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be non-null");
+ } else if (path.isEmpty()) {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be present");
+ } else if (path.charAt(0) != '/') {
+ throw new IllegalArgumentException("The URI path, of the URI " + u + ". must start with a '/'");
+ }
+
+ path = String.format("/%s", UriComponent.decodePath(u.getPath(), true).get(1).toString());
+ WebAppContext context = new WebAppContext();
+ context.setDisplayName("JettyContext");
+ context.setContextPath(path);
+ context.setConfigurations(new Configuration[]{new WebXmlConfiguration()});
+ ServletHolder holder;
+ if (c != null) {
+ holder = context.addServlet(c, "/*");
+ } else {
+ holder = new ServletHolder(servlet);
+ context.addServlet(holder, "/*");
+ }
+
+ if (contextInitParams != null) {
+ for (Map.Entry<String, String> e : contextInitParams.entrySet()) {
+ context.setInitParameter(e.getKey(), e.getValue());
+ }
+ }
+
+ if (initParams != null) {
+ holder.setInitParameters(initParams);
+ }
+
+ Server server = JettyHttpContainerFactory.createServer(u, false);
+ server.setHandler(context);
+ server.start();
+ return server;
+ }
+
+ /**
+ * Create a {@link Server} that registers the declared
+ * servlet instance.
+ *
+ * @param u the URI to create the HTTP server. The URI scheme must be
+ * equal to "http". The URI user information and host
+ * are ignored If the URI port is not present then port 80 will be
+ * used. The URI query and fragment components are ignored. Only first path segment will be used
+ * as context path, the rest will be ignored.
+ * @param servlet the servlet instance.
+ * @param initParams the servlet initialization parameters.
+ * @param contextInitParams the servlet context initialization parameters.
+ * @return the http server, with the endpoint started.
+ * @throws Exception if an error occurs creating the container.
+ * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+ */
+ public static Server create(URI u, Servlet servlet, Map<String, String> initParams, Map<String, String> contextInitParams)
+ throws Exception {
+ if (servlet == null) {
+ throw new IllegalArgumentException("The servlet must not be null");
+ }
+ return create(u, null, servlet, initParams, contextInitParams);
+ }
+}
diff --git a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java
new file mode 100644
index 0000000..0a643c3
--- /dev/null
+++ b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 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 Jetty Servlet container classes.
+ */
+package org.glassfish.jersey.jetty.servlet;
diff --git a/containers/netty-http/pom.xml b/containers/netty-http/pom.xml
new file mode 100644
index 0000000..beb0545
--- /dev/null
+++ b/containers/netty-http/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2016, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-netty-http</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-netty-http</name>
+
+ <description>Netty Http Container.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-netty-connector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java
new file mode 100644
index 0000000..22660e2
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.net.URI;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http2.Http2Codec;
+import io.netty.handler.ssl.ApplicationProtocolNames;
+import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+
+/**
+ * Choose the handler implementation based on Http protocol.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class HttpVersionChooser extends ApplicationProtocolNegotiationHandler {
+
+ private final URI baseUri;
+ private final NettyHttpContainer container;
+
+ HttpVersionChooser(URI baseUri, NettyHttpContainer container) {
+ super(ApplicationProtocolNames.HTTP_1_1);
+
+ this.baseUri = baseUri;
+ this.container = container;
+ }
+
+ @Override
+ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
+ if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
+ ctx.pipeline().addLast(new Http2Codec(true, new JerseyHttp2ServerHandler(baseUri, container)));
+ return;
+ }
+
+ if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
+ ctx.pipeline().addLast(new HttpServerCodec(),
+ new ChunkedWriteHandler(),
+ new JerseyServerHandler(baseUri, container));
+ return;
+ }
+
+ throw new IllegalStateException("Unknown protocol: " + protocol);
+ }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
new file mode 100644
index 0000000..ddb1089
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import javax.ws.rs.core.SecurityContext;
+
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http2.Http2DataFrame;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.netty.connector.internal.NettyInputStream;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+
+/**
+ * Jersey Netty HTTP/2 handler.
+ * <p>
+ * Note that this implementation cannot be more experimental. Any contributions / feedback is welcomed.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@ChannelHandler.Sharable
+class JerseyHttp2ServerHandler extends ChannelDuplexHandler {
+
+ private final URI baseUri;
+ private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();
+ private final NettyHttpContainer container;
+
+ /**
+ * Constructor.
+ *
+ * @param baseUri base {@link URI} of the container (includes context path, if any).
+ * @param container Netty container implementation.
+ */
+ JerseyHttp2ServerHandler(URI baseUri, NettyHttpContainer container) {
+ this.baseUri = baseUri;
+ this.container = container;
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ ctx.close();
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof Http2HeadersFrame) {
+ onHeadersRead(ctx, (Http2HeadersFrame) msg);
+ } else if (msg instanceof Http2DataFrame) {
+ onDataRead(ctx, (Http2DataFrame) msg);
+ } else {
+ super.channelRead(ctx, msg);
+ }
+ }
+
+ /**
+ * Process incoming data.
+ */
+ private void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data) throws Exception {
+ isList.add(new ByteBufInputStream(data.content()));
+ if (data.isEndStream()) {
+ isList.add(NettyInputStream.END_OF_INPUT);
+ }
+ }
+
+ /**
+ * Process incoming request (just a headers in this case, entity is processed separately).
+ */
+ private void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headers) throws Exception {
+
+ final ContainerRequest requestContext = createContainerRequest(ctx, headers);
+
+ requestContext.setWriter(new NettyHttp2ResponseWriter(ctx, headers, container));
+
+ // must be like this, since there is a blocking read from Jersey
+ container.getExecutorService().execute(new Runnable() {
+ @Override
+ public void run() {
+ container.getApplicationHandler().handle(requestContext);
+ }
+ });
+ }
+
+ /**
+ * Create Jersey {@link ContainerRequest} based on Netty {@link HttpRequest}.
+ *
+ * @param ctx Netty channel context.
+ * @param http2Headers Netty Http/2 headers.
+ * @return created Jersey Container Request.
+ */
+ private ContainerRequest createContainerRequest(ChannelHandlerContext ctx, Http2HeadersFrame http2Headers) {
+
+ String path = http2Headers.headers().path().toString();
+
+ String s = path.startsWith("/") ? path.substring(1) : path;
+ URI requestUri = URI.create(baseUri + ContainerUtils.encodeUnsafeCharacters(s));
+
+ ContainerRequest requestContext = new ContainerRequest(
+ baseUri, requestUri, http2Headers.headers().method().toString(), getSecurityContext(),
+ new PropertiesDelegate() {
+
+ private final Map<String, Object> properties = new HashMap<>();
+
+ @Override
+ public Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ return properties.keySet();
+ }
+
+ @Override
+ public void setProperty(String name, Object object) {
+ properties.put(name, object);
+ }
+
+ @Override
+ public void removeProperty(String name) {
+ properties.remove(name);
+ }
+ });
+
+ // request entity handling.
+ if (!http2Headers.isEndStream()) {
+
+ ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+ @Override
+ public void operationComplete(Future<? super Void> future) throws Exception {
+ isList.add(NettyInputStream.END_OF_INPUT_ERROR);
+ }
+ });
+
+ requestContext.setEntityStream(new NettyInputStream(isList));
+ } else {
+ requestContext.setEntityStream(new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return -1;
+ }
+ });
+ }
+
+ // copying headers from netty request to jersey container request context.
+ for (CharSequence name : http2Headers.headers().names()) {
+ requestContext.headers(name.toString(), mapToString(http2Headers.headers().getAll(name)));
+ }
+
+ return requestContext;
+ }
+
+ private List<String> mapToString(List<CharSequence> list) {
+ ArrayList<String> result = new ArrayList<>(list.size());
+
+ for (CharSequence sequence : list) {
+ result.add(sequence.toString());
+ }
+
+ return result;
+ }
+
+ private SecurityContext getSecurityContext() {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ };
+ }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
new file mode 100644
index 0000000..33437e2
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import javax.ws.rs.core.SecurityContext;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.netty.connector.internal.NettyInputStream;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+
+/**
+ * {@link io.netty.channel.ChannelInboundHandler} which servers as a bridge
+ * between Netty and Jersey.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class JerseyServerHandler extends ChannelInboundHandlerAdapter {
+
+ private final URI baseUri;
+ private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();
+ private final NettyHttpContainer container;
+
+ /**
+ * Constructor.
+ *
+ * @param baseUri base {@link URI} of the container (includes context path, if any).
+ * @param container Netty container implementation.
+ */
+ public JerseyServerHandler(URI baseUri, NettyHttpContainer container) {
+ this.baseUri = baseUri;
+ this.container = container;
+ }
+
+ @Override
+ public void channelRead(final ChannelHandlerContext ctx, Object msg) {
+
+ if (msg instanceof HttpRequest) {
+ final HttpRequest req = (HttpRequest) msg;
+
+ if (HttpUtil.is100ContinueExpected(req)) {
+ ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
+ }
+
+ isList.clear(); // clearing the content - possible leftover from previous request processing.
+ final ContainerRequest requestContext = createContainerRequest(ctx, req);
+
+ requestContext.setWriter(new NettyResponseWriter(ctx, req, container));
+
+ // must be like this, since there is a blocking read from Jersey
+ container.getExecutorService().execute(new Runnable() {
+ @Override
+ public void run() {
+ container.getApplicationHandler().handle(requestContext);
+ }
+ });
+ }
+
+ if (msg instanceof HttpContent) {
+ HttpContent httpContent = (HttpContent) msg;
+
+ ByteBuf content = httpContent.content();
+
+ if (content.isReadable()) {
+ isList.add(new ByteBufInputStream(content));
+ }
+
+ if (msg instanceof LastHttpContent) {
+ isList.add(NettyInputStream.END_OF_INPUT);
+ }
+ }
+ }
+
+ /**
+ * Create Jersey {@link ContainerRequest} based on Netty {@link HttpRequest}.
+ *
+ * @param ctx Netty channel context.
+ * @param req Netty Http request.
+ * @return created Jersey Container Request.
+ */
+ private ContainerRequest createContainerRequest(ChannelHandlerContext ctx, HttpRequest req) {
+
+ String s = req.uri().startsWith("/") ? req.uri().substring(1) : req.uri();
+ URI requestUri = URI.create(baseUri + ContainerUtils.encodeUnsafeCharacters(s));
+
+ ContainerRequest requestContext = new ContainerRequest(
+ baseUri, requestUri, req.method().name(), getSecurityContext(),
+ new PropertiesDelegate() {
+
+ private final Map<String, Object> properties = new HashMap<>();
+
+ @Override
+ public Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ return properties.keySet();
+ }
+
+ @Override
+ public void setProperty(String name, Object object) {
+ properties.put(name, object);
+ }
+
+ @Override
+ public void removeProperty(String name) {
+ properties.remove(name);
+ }
+ });
+
+ // request entity handling.
+ if ((req.headers().contains(HttpHeaderNames.CONTENT_LENGTH) && HttpUtil.getContentLength(req) > 0)
+ || HttpUtil.isTransferEncodingChunked(req)) {
+
+ ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+ @Override
+ public void operationComplete(Future<? super Void> future) throws Exception {
+ isList.add(NettyInputStream.END_OF_INPUT_ERROR);
+ }
+ });
+
+ requestContext.setEntityStream(new NettyInputStream(isList));
+ } else {
+ requestContext.setEntityStream(new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return -1;
+ }
+ });
+ }
+
+ // copying headers from netty request to jersey container request context.
+ for (String name : req.headers().names()) {
+ requestContext.headers(name, req.headers().getAll(name));
+ }
+
+ return requestContext;
+ }
+
+ private SecurityContext getSecurityContext() {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ ctx.close();
+ }
+
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java
new file mode 100644
index 0000000..16a6ce0
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.net.URI;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpMessage;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler;
+import io.netty.handler.codec.http2.Http2Codec;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.util.AsciiString;
+
+/**
+ * Jersey {@link ChannelInitializer}.
+ * <p>
+ * Adds {@link HttpServerCodec}, {@link ChunkedWriteHandler} and {@link JerseyServerHandler} to the channels pipeline.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class JerseyServerInitializer extends ChannelInitializer<SocketChannel> {
+
+ private final URI baseUri;
+ private final SslContext sslCtx;
+ private final NettyHttpContainer container;
+ private final boolean http2;
+
+ /**
+ * Constructor.
+ *
+ * @param baseUri base {@link URI} of the container (includes context path, if any).
+ * @param sslCtx SSL context.
+ * @param container Netty container implementation.
+ */
+ public JerseyServerInitializer(URI baseUri, SslContext sslCtx, NettyHttpContainer container) {
+ this(baseUri, sslCtx, container, false);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param baseUri base {@link URI} of the container (includes context path, if any).
+ * @param sslCtx SSL context.
+ * @param container Netty container implementation.
+ * @param http2 Http/2 protocol support.
+ */
+ public JerseyServerInitializer(URI baseUri, SslContext sslCtx, NettyHttpContainer container, boolean http2) {
+ this.baseUri = baseUri;
+ this.sslCtx = sslCtx;
+ this.container = container;
+ this.http2 = http2;
+ }
+
+ @Override
+ public void initChannel(SocketChannel ch) {
+ if (http2) {
+
+ if (sslCtx != null) {
+ configureSsl(ch);
+ } else {
+ configureClearText(ch);
+ }
+
+ } else {
+ ChannelPipeline p = ch.pipeline();
+ if (sslCtx != null) {
+ p.addLast(sslCtx.newHandler(ch.alloc()));
+ }
+ p.addLast(new HttpServerCodec());
+ p.addLast(new ChunkedWriteHandler());
+ p.addLast(new JerseyServerHandler(baseUri, container));
+ }
+ }
+
+ /**
+ * Configure the pipeline for TLS NPN negotiation to HTTP/2.
+ */
+ private void configureSsl(SocketChannel ch) {
+ ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new HttpVersionChooser(baseUri, container));
+ }
+
+ /**
+ * Configure the pipeline for a cleartext upgrade from HTTP to HTTP/2.
+ */
+ private void configureClearText(SocketChannel ch) {
+ final ChannelPipeline p = ch.pipeline();
+ final HttpServerCodec sourceCodec = new HttpServerCodec();
+
+ p.addLast(sourceCodec);
+ p.addLast(new HttpServerUpgradeHandler(sourceCodec, new HttpServerUpgradeHandler.UpgradeCodecFactory() {
+ @Override
+ public HttpServerUpgradeHandler.UpgradeCodec newUpgradeCodec(CharSequence protocol) {
+ if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
+ return new Http2ServerUpgradeCodec(new Http2Codec(true, new JerseyHttp2ServerHandler(baseUri, container)));
+ } else {
+ return null;
+ }
+ }
+ }));
+ p.addLast(new SimpleChannelInboundHandler<HttpMessage>() {
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, HttpMessage msg) throws Exception {
+ // If this handler is hit then no upgrade has been attempted and the client is just talking HTTP.
+ // "Directly talking: " + msg.protocolVersion() + " (no upgrade was attempted)");
+
+ ChannelPipeline pipeline = ctx.pipeline();
+ ChannelHandlerContext thisCtx = pipeline.context(this);
+ pipeline.addAfter(thisCtx.name(), null, new JerseyServerHandler(baseUri, container));
+ pipeline.replace(this, null, new ChunkedWriteHandler());
+ ctx.fireChannelRead(msg);
+ }
+ });
+ }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java
new file mode 100644
index 0000000..327d894
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+
+/**
+ * Netty implementation of {@link ContainerResponseWriter}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyHttp2ResponseWriter implements ContainerResponseWriter {
+
+ private final ChannelHandlerContext ctx;
+ private final Http2HeadersFrame headersFrame;
+ private final NettyHttpContainer container;
+
+ private volatile ScheduledFuture<?> suspendTimeoutFuture;
+ private volatile Runnable suspendTimeoutHandler;
+
+ NettyHttp2ResponseWriter(ChannelHandlerContext ctx, Http2HeadersFrame headersFrame, NettyHttpContainer container) {
+ this.ctx = ctx;
+ this.headersFrame = headersFrame;
+ this.container = container;
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse responseContext)
+ throws ContainerException {
+
+ String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+ int statusCode = responseContext.getStatus();
+
+ HttpResponseStatus status = reasonPhrase == null
+ ? HttpResponseStatus.valueOf(statusCode)
+ : new HttpResponseStatus(statusCode, reasonPhrase);
+
+ DefaultHttp2Headers response = new DefaultHttp2Headers();
+ response.status(Integer.toString(responseContext.getStatus()));
+
+ for (final Map.Entry<String, List<String>> e : responseContext.getStringHeaders().entrySet()) {
+ response.add(e.getKey().toLowerCase(), e.getValue());
+ }
+
+ response.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(contentLength));
+
+ ctx.writeAndFlush(new DefaultHttp2HeadersFrame(response));
+
+ if (!headersFrame.headers().method().equals(HttpMethod.HEAD.asciiName())
+ && (contentLength > 0 || contentLength == -1)) {
+
+ return new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ write(new byte[]{(byte) b});
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+
+ ByteBuf buffer = ctx.alloc().buffer(len);
+ buffer.writeBytes(b, off, len);
+
+ ctx.writeAndFlush(new DefaultHttp2DataFrame(buffer, false));
+ }
+
+ @Override
+ public void flush() throws IOException {
+ ctx.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ ctx.write(new DefaultHttp2DataFrame(true)).addListener(NettyResponseWriter.FLUSH_FUTURE);
+ }
+ };
+
+ } else {
+ ctx.writeAndFlush(new DefaultHttp2DataFrame(true));
+ return null;
+ }
+ }
+
+ @Override
+ public boolean suspend(long timeOut, TimeUnit timeUnit, final ContainerResponseWriter.TimeoutHandler
+ timeoutHandler) {
+
+ suspendTimeoutHandler = new Runnable() {
+ @Override
+ public void run() {
+ timeoutHandler.onTimeout(NettyHttp2ResponseWriter.this);
+ }
+ };
+
+ if (timeOut <= 0) {
+ return true;
+ }
+
+ suspendTimeoutFuture =
+ container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+
+ return true;
+ }
+
+ @Override
+ public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException {
+
+ // suspend(0, .., ..) was called, so suspendTimeoutFuture is null.
+ if (suspendTimeoutFuture != null) {
+ suspendTimeoutFuture.cancel(true);
+ }
+
+ if (timeOut <= 0) {
+ return;
+ }
+
+ suspendTimeoutFuture =
+ container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+ }
+
+ @Override
+ public void commit() {
+ ctx.flush();
+ }
+
+ @Override
+ public void failure(Throwable error) {
+ ctx.writeAndFlush(new DefaultHttp2Headers().status(HttpResponseStatus.INTERNAL_SERVER_ERROR.codeAsText()))
+ .addListener(ChannelFutureListener.CLOSE);
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true;
+ }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java
new file mode 100644
index 0000000..c825a82
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.spi.ExecutorServiceProvider;
+import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
+
+/**
+ * Netty based implementation of a {@link Container}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyHttpContainer implements Container {
+
+ private volatile ApplicationHandler appHandler;
+
+ public NettyHttpContainer(Application application) {
+ this.appHandler = new ApplicationHandler(application);
+ this.appHandler.onStartup(this);
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ @Override
+ public void reload() {
+ reload(appHandler.getConfiguration());
+ }
+
+ @Override
+ public void reload(ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration);
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ }
+
+ /**
+ * Get {@link java.util.concurrent.ExecutorService}.
+ *
+ * @return Executor service associated with this container.
+ */
+ ExecutorService getExecutorService() {
+ return appHandler.getInjectionManager().getInstance(ExecutorServiceProvider.class).getExecutorService();
+ }
+
+ /**
+ * Get {@link ScheduledExecutorService}.
+ *
+ * @return Scheduled executor service associated with this container.
+ */
+ ScheduledExecutorService getScheduledExecutorService() {
+ return appHandler.getInjectionManager().getInstance(ScheduledExecutorServiceProvider.class).getExecutorService();
+ }
+}
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
new file mode 100644
index 0000000..124b36e
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.ssl.SslContext;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+/**
+ * Netty implementation of {@link ContainerProvider}.
+ * <p>
+ * There is also a few "factory" methods for creating Netty server.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.24
+ */
+@Beta
+public class NettyHttpContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+ if (NettyHttpContainer.class == type) {
+ return type.cast(new NettyHttpContainer(application));
+ }
+
+ return null;
+ }
+
+ /**
+ * Create and start Netty server.
+ *
+ * @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 {
+
+ // Configure the server.
+ final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ final EventLoopGroup workerGroup = new NioEventLoopGroup();
+ final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.option(ChannelOption.SO_BACKLOG, 1024);
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+
+ int port = getPort(baseUri);
+
+ Channel ch = b.bind(port).sync().channel();
+
+ ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+ @Override
+ public void operationComplete(Future<? super Void> future) throws Exception {
+ container.getApplicationHandler().onShutdown(container);
+
+ bossGroup.shutdownGracefully();
+ workerGroup.shutdownGracefully();
+ }
+ });
+
+ if (block) {
+ ch.closeFuture().sync();
+ return ch;
+ } else {
+ return ch;
+ }
+ } catch (InterruptedException e) {
+ throw new ProcessingException(e);
+ }
+ }
+
+ /**
+ * 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.
+ * @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, final boolean block)
+ throws ProcessingException {
+
+ return createServer(baseUri, configuration, null, block);
+ }
+
+ /**
+ * Create and start Netty HTTP/2 server.
+ * <p>
+ * The server is capable of connection upgrade to HTTP/2. HTTP/1.x request will be server as they were used to.
+ * <p>
+ * Note that this implementation cannot be more experimental. Any contributions / feedback is welcomed.
+ *
+ * @param baseUri base uri.
+ * @param configuration Jersey configuration.
+ * @param sslContext Netty {@link SslContext}.
+ * @return Netty channel instance.
+ * @throws ProcessingException when there is an issue with creating new container.
+ */
+ public static Channel createHttp2Server(final URI baseUri, final ResourceConfig configuration, SslContext sslContext) throws
+ ProcessingException {
+
+ final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ final EventLoopGroup workerGroup = new NioEventLoopGroup();
+ final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.option(ChannelOption.SO_BACKLOG, 1024);
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new JerseyServerInitializer(baseUri, sslContext, container, true));
+
+ int port = getPort(baseUri);
+
+ Channel ch = b.bind(port).sync().channel();
+
+ ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+ @Override
+ public void operationComplete(Future<? super Void> future) throws Exception {
+ container.getApplicationHandler().onShutdown(container);
+
+ bossGroup.shutdownGracefully();
+ workerGroup.shutdownGracefully();
+ }
+ });
+
+ return ch;
+
+ } catch (InterruptedException e) {
+ throw new ProcessingException(e);
+ }
+ }
+
+ private static int getPort(URI uri) {
+ if (uri.getPort() == -1) {
+ if ("http".equalsIgnoreCase(uri.getScheme())) {
+ return 80;
+ } else if ("https".equalsIgnoreCase(uri.getScheme())) {
+ return 443;
+ }
+
+ throw new IllegalArgumentException("URI scheme must be 'http' or 'https'.");
+ }
+
+ return uri.getPort();
+ }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java
new file mode 100644
index 0000000..e50d870
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2016, 2018 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.netty.httpserver;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.netty.connector.internal.JerseyChunkedInput;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.DefaultHttpResponse;
+import io.netty.handler.codec.http.HttpChunkedInput;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.LastHttpContent;
+
+/**
+ * Netty implementation of {@link ContainerResponseWriter}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyResponseWriter implements ContainerResponseWriter {
+
+ private static final Logger LOGGER = Logger.getLogger(NettyResponseWriter.class.getName());
+
+ static final ChannelFutureListener FLUSH_FUTURE = new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ future.channel().flush();
+ }
+ };
+
+ private final ChannelHandlerContext ctx;
+ private final HttpRequest req;
+ private final NettyHttpContainer container;
+
+ private volatile ScheduledFuture<?> suspendTimeoutFuture;
+ private volatile Runnable suspendTimeoutHandler;
+
+ private boolean responseWritten = false;
+
+ NettyResponseWriter(ChannelHandlerContext ctx, HttpRequest req, NettyHttpContainer container) {
+ this.ctx = ctx;
+ this.req = req;
+ this.container = container;
+ }
+
+ @Override
+ public synchronized OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse responseContext)
+ throws ContainerException {
+
+ if (responseWritten) {
+ LOGGER.log(Level.FINE, "Response already written.");
+ return null;
+ }
+
+ responseWritten = true;
+
+ String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+ int statusCode = responseContext.getStatus();
+
+ HttpResponseStatus status = reasonPhrase == null
+ ? HttpResponseStatus.valueOf(statusCode)
+ : new HttpResponseStatus(statusCode, reasonPhrase);
+
+ DefaultHttpResponse response;
+ if (contentLength == 0) {
+ response = new DefaultFullHttpResponse(req.protocolVersion(), status);
+ } else {
+ response = new DefaultHttpResponse(req.protocolVersion(), status);
+ }
+
+ for (final Map.Entry<String, List<String>> e : responseContext.getStringHeaders().entrySet()) {
+ response.headers().add(e.getKey(), e.getValue());
+ }
+
+ if (contentLength == -1) {
+ HttpUtil.setTransferEncodingChunked(response, true);
+ } else {
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
+ }
+
+ if (HttpUtil.isKeepAlive(req)) {
+ response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
+ }
+
+ ctx.writeAndFlush(response);
+
+ if (req.method() != HttpMethod.HEAD && (contentLength > 0 || contentLength == -1)) {
+
+ JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(ctx.channel());
+
+ if (HttpUtil.isTransferEncodingChunked(response)) {
+ ctx.write(new HttpChunkedInput(jerseyChunkedInput)).addListener(FLUSH_FUTURE);
+ } else {
+ ctx.write(new HttpChunkedInput(jerseyChunkedInput)).addListener(FLUSH_FUTURE);
+ }
+ return jerseyChunkedInput;
+
+ } else {
+ ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean suspend(long timeOut, TimeUnit timeUnit, final ContainerResponseWriter.TimeoutHandler
+ timeoutHandler) {
+
+ suspendTimeoutHandler = new Runnable() {
+ @Override
+ public void run() {
+ timeoutHandler.onTimeout(NettyResponseWriter.this);
+ }
+ };
+
+ if (timeOut <= 0) {
+ return true;
+ }
+
+ suspendTimeoutFuture =
+ container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+
+ return true;
+ }
+
+ @Override
+ public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException {
+
+ // suspend(0, .., ..) was called, so suspendTimeoutFuture is null.
+ if (suspendTimeoutFuture != null) {
+ suspendTimeoutFuture.cancel(true);
+ }
+
+ if (timeOut <= 0) {
+ return;
+ }
+
+ suspendTimeoutFuture =
+ container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+ }
+
+ @Override
+ public void commit() {
+ ctx.flush();
+ }
+
+ @Override
+ public void failure(Throwable error) {
+ ctx.writeAndFlush(new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.INTERNAL_SERVER_ERROR))
+ .addListener(ChannelFutureListener.CLOSE);
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return true;
+ }
+}
diff --git a/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..96ee927
--- /dev/null
+++ b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.netty.httpserver.NettyHttpContainerProvider
diff --git a/containers/pom.xml b/containers/pom.xml
new file mode 100644
index 0000000..2246a14
--- /dev/null
+++ b/containers/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-containers</name>
+
+ <description>Jersey container providers umbrella project module</description>
+
+ <modules>
+ <module>glassfish</module>
+ <module>grizzly2-http</module>
+ <module>grizzly2-servlet</module>
+ <module>jdk-http</module>
+ <module>jersey-servlet-core</module>
+ <module>jersey-servlet</module>
+ <module>jetty-http</module>
+ <module>jetty-servlet</module>
+ <module>netty-http</module>
+ <module>simple-http</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/containers/simple-http/pom.xml b/containers/simple-http/pom.xml
new file mode 100644
index 0000000..e3bfb38
--- /dev/null
+++ b/containers/simple-http/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 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
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-container-simple-http</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-container-simple-http</name>
+
+ <description>Simple Http Container</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.simpleframework</groupId>
+ <artifactId>simple-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.simpleframework</groupId>
+ <artifactId>simple-transport</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.simpleframework</groupId>
+ <artifactId>simple-common</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+</project>
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
new file mode 100644
index 0000000..e59962f
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.process.internal.RequestScoped;
+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.ResourceConfig;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler;
+
+import org.simpleframework.common.thread.DaemonFactory;
+import org.simpleframework.http.Address;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.Status;
+
+/**
+ * Jersey {@code Container} implementation based on Simple framework
+ * {@link org.simpleframework.http.core.Container}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class SimpleContainer implements org.simpleframework.http.core.Container, Container {
+
+ private static final ExtendedLogger logger =
+ new ExtendedLogger(Logger.getLogger(SimpleContainer.class.getName()), Level.FINEST);
+
+ private final Type RequestTYPE = (new GenericType<Ref<Request>>() { }).getType();
+ private final Type ResponseTYPE = (new GenericType<Ref<Response>>() { }).getType();
+
+ /**
+ * Referencing factory for Simple request.
+ */
+ private static class SimpleRequestReferencingFactory extends ReferencingFactory<Request> {
+
+ @Inject
+ public SimpleRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * Referencing factory for Simple response.
+ */
+ private static class SimpleResponseReferencingFactory extends ReferencingFactory<Response> {
+
+ @Inject
+ public SimpleResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+ super(referenceFactory);
+ }
+ }
+
+ /**
+ * An internal binder to enable Simple HTTP container specific types injection. This binder allows
+ * to inject underlying Grizzly HTTP request and response instances.
+ */
+ private static class SimpleBinder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bindFactory(SimpleRequestReferencingFactory.class).to(Request.class).proxy(true)
+ .proxyForSameScope(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Request>referenceFactory())
+ .to(new GenericType<Ref<Request>>() {
+ }).in(RequestScoped.class);
+
+ bindFactory(SimpleResponseReferencingFactory.class).to(Response.class).proxy(true)
+ .proxyForSameScope(false).in(RequestScoped.class);
+ bindFactory(ReferencingFactory.<Response>referenceFactory())
+ .to(new GenericType<Ref<Response>>() {
+ }).in(RequestScoped.class);
+ }
+ }
+
+ private volatile ScheduledExecutorService scheduler;
+ private volatile ApplicationHandler appHandler;
+
+ private static final class ResponseWriter implements ContainerResponseWriter {
+
+ private final AtomicReference<TimeoutTimer> reference;
+ private final ScheduledExecutorService scheduler;
+ private final Response response;
+
+ ResponseWriter(final Response response, final ScheduledExecutorService scheduler) {
+ this.reference = new AtomicReference<TimeoutTimer>();
+ this.response = response;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(final long contentLength,
+ final ContainerResponse context) throws ContainerException {
+ final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+
+ final int code = statusInfo.getStatusCode();
+ final String reason = statusInfo.getReasonPhrase() == null
+ ? Status.getDescription(code)
+ : statusInfo.getReasonPhrase();
+ response.setCode(code);
+ response.setDescription(reason);
+
+ if (contentLength != -1) {
+ response.setContentLength(contentLength);
+ }
+ for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+ for (final String value : e.getValue()) {
+ response.addValue(e.getKey(), value);
+ }
+ }
+
+ try {
+ return response.getOutputStream();
+ } catch (final IOException ioe) {
+ throw new ContainerException("Error during writing out the response headers.", ioe);
+ }
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit,
+ final TimeoutHandler timeoutHandler) {
+ try {
+ TimeoutTimer timer = reference.get();
+
+ if (timer == null) {
+ TimeoutDispatcher task = new TimeoutDispatcher(this, timeoutHandler);
+ ScheduledFuture<?> future =
+ scheduler.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+ timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+ timer = new TimeoutTimer(scheduler, future, task);
+ reference.set(timer);
+ return true;
+ }
+ return false;
+ } catch (final IllegalStateException ex) {
+ return false;
+ } finally {
+ logger.debugLog("suspend(...) called");
+ }
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit)
+ throws IllegalStateException {
+ try {
+ TimeoutTimer timer = reference.get();
+
+ if (timer == null) {
+ throw new IllegalStateException("Response has not been suspended");
+ }
+ timer.reschedule(timeOut, timeUnit);
+ } finally {
+ logger.debugLog("setTimeout(...) called");
+ }
+ }
+
+ @Override
+ public void commit() {
+ try {
+ response.close();
+ } catch (final IOException e) {
+ logger.log(Level.SEVERE, "Unable to send 500 error response.", e);
+ } finally {
+ logger.debugLog("commit() called");
+ }
+ }
+
+ public boolean isSuspended() {
+ return reference.get() != null;
+ }
+
+ @Override
+ public void failure(final Throwable error) {
+ try {
+ if (!response.isCommitted()) {
+ response.setCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ response.setDescription(error.getMessage());
+ }
+ } finally {
+ logger.debugLog("failure(...) called");
+ commit();
+ rethrow(error);
+ }
+
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return false;
+ }
+
+ /**
+ * Rethrow the original exception as required by JAX-RS, 3.3.4
+ *
+ * @param error throwable to be re-thrown
+ */
+ private void rethrow(final Throwable error) {
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+
+ }
+
+ private static final class TimeoutTimer {
+
+ private final AtomicReference<ScheduledFuture<?>> reference;
+ private final ScheduledExecutorService service;
+ private final TimeoutDispatcher task;
+
+ public TimeoutTimer(ScheduledExecutorService service, ScheduledFuture<?> future,
+ TimeoutDispatcher task) {
+ this.reference = new AtomicReference<ScheduledFuture<?>>();
+ this.service = service;
+ this.task = task;
+ }
+
+ public void reschedule(long timeOut, TimeUnit timeUnit) {
+ ScheduledFuture<?> future = reference.getAndSet(null);
+
+ if (future != null) {
+ if (future.cancel(false)) {
+ future = service.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+ timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+ reference.set(future);
+ }
+ } else {
+ future = service.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+ timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+ reference.set(future);
+ }
+ }
+ }
+
+ private static final class TimeoutDispatcher implements Runnable {
+
+ private final ResponseWriter writer;
+ private final TimeoutHandler handler;
+
+ public TimeoutDispatcher(ResponseWriter writer, TimeoutHandler handler) {
+ this.writer = writer;
+ this.handler = handler;
+ }
+
+ public void run() {
+ try {
+ handler.onTimeout(writer);
+ } catch (Exception e) {
+ logger.log(Level.INFO, "Failed to call timeout handler", e);
+ }
+ }
+ }
+
+ @Override
+ public void handle(final Request request, final Response response) {
+ final ResponseWriter responseWriter = new ResponseWriter(response, scheduler);
+ final URI baseUri = getBaseUri(request);
+ final URI requestUri = getRequestUri(request, baseUri);
+
+ try {
+ final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri,
+ request.getMethod(), getSecurityContext(request), new MapPropertiesDelegate());
+ requestContext.setEntityStream(request.getInputStream());
+ for (final String headerName : request.getNames()) {
+ requestContext.headers(headerName, request.getValue(headerName));
+ }
+ requestContext.setWriter(responseWriter);
+ requestContext.setRequestScopedInitializer(injectionManager -> {
+ injectionManager.<Ref<Request>>getInstance(RequestTYPE).set(request);
+ injectionManager.<Ref<Response>>getInstance(ResponseTYPE).set(response);
+ });
+
+ appHandler.handle(requestContext);
+ } catch (final Exception ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ if (!responseWriter.isSuspended()) {
+ close(response);
+ }
+ }
+ }
+
+ private URI getRequestUri(final Request request, final URI baseUri) {
+ try {
+ final String serverAddress = getServerAddress(baseUri);
+ String uri = ContainerUtils.getHandlerPath(request.getTarget());
+
+ final String queryString = request.getQuery().toString();
+ if (queryString != null) {
+ uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+ }
+
+ return new URI(serverAddress + uri);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private String getServerAddress(final URI baseUri) throws URISyntaxException {
+ return new URI(baseUri.getScheme(), null, baseUri.getHost(), baseUri.getPort(), null, null,
+ null).toString();
+ }
+
+ private URI getBaseUri(final Request request) {
+ try {
+ final String hostHeader = request.getValue("Host");
+
+ if (hostHeader != null) {
+ final String scheme = request.isSecure() ? "https" : "http";
+ return new URI(scheme + "://" + hostHeader + "/");
+ } else {
+ final Address address = request.getAddress();
+ return new URI(address.getScheme(), null, address.getDomain(), address.getPort(), "/", null,
+ null);
+ }
+ } catch (final URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ private SecurityContext getSecurityContext(final Request request) {
+ return new SecurityContext() {
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.isSecure();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ };
+ }
+
+ private void close(final Response response) {
+ try {
+ response.close();
+ } catch (final Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration.register(new SimpleBinder()));
+ scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ /**
+ * Inform this container that the server has been started.
+ * <p/>
+ * This method must be implicitly called after the server containing this container is started.
+ */
+ void onServerStart() {
+ appHandler.onStartup(this);
+ }
+
+ /**
+ * Inform this container that the server is being stopped.
+ * <p/>
+ * This method must be implicitly called before the server containing this container is stopped.
+ */
+ void onServerStop() {
+ appHandler.onShutdown(this);
+ scheduler.shutdown();
+ }
+
+ /**
+ * Create a new Simple framework HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP container.
+ * @param parentContext DI provider specific context with application's registered bindings.
+ */
+ SimpleContainer(final Application application, final Object parentContext) {
+ this.appHandler = new ApplicationHandler(application, new SimpleBinder(), parentContext);
+ this.scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+ }
+
+ /**
+ * Create a new Simple framework HTTP container.
+ *
+ * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP
+ * container.
+ */
+ SimpleContainer(final Application application) {
+ this.appHandler = new ApplicationHandler(application, new SimpleBinder());
+ this.scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+ }
+}
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
new file mode 100644
index 0000000..a9dc46d
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+
+import javax.net.ssl.SSLContext;
+
+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.SocketProcessor;
+import org.simpleframework.transport.connect.Connection;
+import org.simpleframework.transport.connect.SocketConnection;
+
+/**
+ * Factory for creating and starting Simple server containers. This returns a handle to the started
+ * server as {@link Closeable} instances, which allows the server to be stopped by invoking the
+ * {@link Closeable#close} method.
+ * <p/>
+ * To start the server in HTTPS mode an {@link SSLContext} can be provided. This will be used to
+ * decrypt and encrypt information sent over the connected TCP socket channel.
+ *
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Paul Sandoz
+ */
+public final class SimpleContainerFactory {
+
+ private SimpleContainerFactory() {
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes declared by the resource configuration.
+ *
+ * @param address the URI to create the http server. The URI scheme must be equal to "http". The
+ * URI user information and host are ignored If the URI port is not present then port 80
+ * will be used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @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 ResourceConfig config) {
+ return create(address, null, new SimpleContainer(config));
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes declared by the resource configuration.
+ *
+ * @param address the URI to create the http server. The URI scheme must be equal to "http". The
+ * URI user information and host are ignored If the URI port is not present then port 80
+ * will be used. The URI path, query and fragment components are ignored.
+ * @param config the resource configuration.
+ * @param count this is the number of threads to be used.
+ * @param select this is the number of selector threads to use.
+ * @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 ResourceConfig config, final int count,
+ final int select) {
+ return create(address, null, new SimpleContainer(config), count, select);
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes declared by the resource configuration.
+ *
+ * @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.
+ * @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 ResourceConfig config) {
+ return create(address, context, new SimpleContainer(config));
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes declared by the resource configuration.
+ *
+ * @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 count this is the number of threads to be used.
+ * @param select this is the number of selector threads to use.
+ * @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 ResourceConfig config, final int count, final int select) {
+ return create(address, context, new SimpleContainer(config), count, select);
+ }
+
+ /**
+ * 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 container the container that handles all HTTP requests.
+ * @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 SimpleContainer container) {
+ return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+ @Override
+ public SocketProcessor get() throws IOException {
+ return new ContainerSocketProcessor(container);
+ }
+ });
+ }
+
+ /**
+ * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+ * resource and provider classes declared by the resource configuration.
+ *
+ * @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 parentContext DI provider specific context with application's registered bindings.
+ * @param count this is the number of threads to be used.
+ * @param select this is the number of selector threads to use.
+ * @return the closeable connection, with the endpoint started.
+ * @throws ProcessingException thrown when problems during server creation.
+ * @throws IllegalArgumentException if {@code address} is {@code null}.
+ * @since 2.12
+ */
+ public static SimpleServer create(final URI address, final SSLContext context,
+ final ResourceConfig config, final Object parentContext, final int count,
+ final int select) {
+ return create(address, context, new SimpleContainer(config, parentContext), count, select);
+ }
+
+ /**
+ * 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 container the container that handles all HTTP requests.
+ * @param count this is the number of threads to be used.
+ * @param select this is the number of selector threads to use.
+ * @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 SimpleContainer container, final int count, final int select)
+ throws ProcessingException {
+
+ return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+ @Override
+ public SocketProcessor get() throws IOException {
+ return new ContainerSocketProcessor(container, count, select);
+ }
+ });
+ }
+
+ private static SimpleServer _create(final URI address, final SSLContext context,
+ final SimpleContainer container,
+ final UnsafeValue<SocketProcessor, IOException> serverProvider)
+ throws ProcessingException {
+ if (address == null) {
+ throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
+ }
+ final String scheme = address.getScheme();
+ int defaultPort = org.glassfish.jersey.server.spi.Container.DEFAULT_HTTP_PORT;
+
+ if (context == null) {
+ if (!scheme.equalsIgnoreCase("http")) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP());
+ }
+ } else {
+ if (!scheme.equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS());
+ }
+ defaultPort = org.glassfish.jersey.server.spi.Container.DEFAULT_HTTPS_PORT;
+ }
+ int port = address.getPort();
+
+ if (port == -1) {
+ port = defaultPort;
+ }
+ final InetSocketAddress listen = new InetSocketAddress(port);
+ final Connection connection;
+ try {
+ final SimpleTraceAnalyzer analyzer = new SimpleTraceAnalyzer();
+ final SocketProcessor server = serverProvider.get();
+ connection = new SocketConnection(server, analyzer);
+
+
+ final SocketAddress socketAddr = connection.connect(listen, context);
+ container.onServerStart();
+
+ return new SimpleServer() {
+
+ @Override
+ public void close() throws IOException {
+ container.onServerStop();
+ analyzer.stop();
+ connection.close();
+ }
+
+ @Override
+ public int getPort() {
+ return ((InetSocketAddress) socketAddr).getPort();
+ }
+
+ @Override
+ public boolean isDebug() {
+ return analyzer.isActive();
+ }
+
+ @Override
+ public void setDebug(boolean enable) {
+ if (enable) {
+ analyzer.start();
+ } else {
+ analyzer.stop();
+ }
+ }
+ };
+ } 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/SimpleContainerProvider.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerProvider.java
new file mode 100644
index 0000000..68f8f0f
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.simpleframework.http.core.Container;
+
+/**
+ * Container provider for containers based on Simple HTTP Server
+ * {@link org.simpleframework.http.core.Container}.
+ *
+ * @author Marc Hadley
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class SimpleContainerProvider implements ContainerProvider {
+
+ @Override
+ public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+ if (Container.class == type || SimpleContainer.class == type) {
+ return type.cast(new SimpleContainer(application));
+ }
+ return 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
new file mode 100644
index 0000000..02aec75
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2014, 2018 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.simple;
+
+import java.io.Closeable;
+
+/**
+ * Simple server facade providing convenient methods to obtain info about the server (i.e. port).
+ *
+ * @author Michal Gajdos
+ * @since 2.9
+ */
+public interface SimpleServer extends Closeable {
+
+ /**
+ * 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.
+ *
+ * @return the port the server is listening on
+ */
+ public int getPort();
+
+ /**
+ * If this is true then very low level I/O operations are logged. Typically this is used to debug
+ * I/O issues such as HTTPS handshakes or performance issues by analysing the various latencies
+ * involved in the HTTP conversation.
+ * <p/>
+ * There is a minimal performance penalty if this is enabled and it is perfectly suited to being
+ * enabled in a production environment, at the cost of logging overhead.
+ *
+ * @return {@code true} if debug is enabled, false otherwise.
+ * @since 2.23
+ */
+ public boolean isDebug();
+
+ /**
+ * To enable very low level logging this can be enabled. This goes far beyond logging issues such
+ * as connection establishment of request dispatch, it can trace the TCP operations latencies
+ * involved.
+ *
+ * @param enable if {@code true} debug tracing will be enabled.
+ * @since 2.23
+ */
+ public void setDebug(boolean enable);
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java
new file mode 100644
index 0000000..4873da1
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2014, 2018 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.simple;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.channels.SelectableChannel;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+
+import org.simpleframework.common.thread.DaemonFactory;
+import org.simpleframework.transport.trace.Trace;
+import org.simpleframework.transport.trace.TraceAnalyzer;
+
+/**
+ * Tracing at a very low level can be performed with a {@link TraceAnalyzer}. This provides much
+ * more useful information than the conventional {@link LoggingFilter} in that it provides details
+ * at a very low level. This is very useful when monitoring performance interactions at the TCP
+ * level between clients and servers.
+ * <p/>
+ * Performance overhead for the server is minimal as events are pumped out in batches. The amount of
+ * logging information will increase quite significantly though.
+ *
+ * @author Niall Gallagher
+ */
+public class SimpleTraceAnalyzer implements TraceAnalyzer {
+
+ private static final ExtendedLogger logger =
+ new ExtendedLogger(Logger.getLogger(SimpleTraceAnalyzer.class.getName()), Level.FINEST);
+
+ private final TraceConsumer consumer;
+ private final ThreadFactory factory;
+ private final AtomicBoolean active;
+ private final AtomicLong count;
+
+ /**
+ * Creates an asynchronous trace event logger.
+ */
+ public SimpleTraceAnalyzer() {
+ this.factory = new DaemonFactory(TraceConsumer.class);
+ this.consumer = new TraceConsumer();
+ this.active = new AtomicBoolean();
+ this.count = new AtomicLong();
+ }
+
+ public boolean isActive() {
+ return active.get();
+ }
+
+ @Override
+ public Trace attach(SelectableChannel channel) {
+ long sequence = count.getAndIncrement();
+ return new TraceFeeder(channel, sequence);
+ }
+
+ /**
+ * Begin logging trace events to the underlying logger.
+ */
+ public void start() {
+ if (active.compareAndSet(false, true)) {
+ Thread thread = factory.newThread(consumer);
+ thread.start();
+ }
+ }
+
+ @Override
+ public void stop() {
+ active.set(false);
+ }
+
+ private class TraceConsumer implements Runnable {
+
+ private final Queue<TraceRecord> queue;
+
+ public TraceConsumer() {
+ this.queue = new ConcurrentLinkedQueue<TraceRecord>();
+ }
+
+ public void consume(TraceRecord record) {
+ queue.offer(record);
+ }
+
+ public void run() {
+ try {
+ while (active.get()) {
+ Thread.sleep(1000);
+ drain();
+ }
+ } catch (Exception e) {
+ logger.info("Trace analyzer error");
+ } finally {
+ try {
+ drain();
+ } catch (Exception e) {
+ logger.info("Trace analyzer could not drain queue");
+ }
+ active.set(false);
+ }
+
+ }
+
+ private void drain() {
+ while (!queue.isEmpty()) {
+ TraceRecord record = queue.poll();
+
+ if (record != null) {
+ String message = record.toString();
+ logger.info(message);
+ }
+ }
+ }
+ }
+
+ private class TraceFeeder implements Trace {
+
+ private final SelectableChannel channel;
+ private final long sequence;
+
+ public TraceFeeder(SelectableChannel channel, long sequence) {
+ this.sequence = sequence;
+ this.channel = channel;
+ }
+
+ @Override
+ public void trace(Object event) {
+ trace(event, null);
+ }
+
+ @Override
+ public void trace(Object event, Object value) {
+ if (active.get()) {
+ TraceRecord record = new TraceRecord(channel, event, value, sequence);
+ consumer.consume(record);
+ }
+ }
+
+ }
+
+ private class TraceRecord {
+
+ private final SelectableChannel channel;
+ private final String thread;
+ private final Object event;
+ private final Object value;
+ private final long sequence;
+
+ public TraceRecord(SelectableChannel channel, Object event, Object value, long sequence) {
+ this.thread = Thread.currentThread().getName();
+ this.sequence = sequence;
+ this.channel = channel;
+ this.event = event;
+ this.value = value;
+ }
+
+ public String toString() {
+ StringWriter builder = new StringWriter();
+ PrintWriter writer = new PrintWriter(builder);
+
+ writer.print(sequence);
+ writer.print(" ");
+ writer.print(channel);
+ writer.print(" (");
+ writer.print(thread);
+ writer.print("): ");
+ writer.print(event);
+
+ if (value != null) {
+ if (value instanceof Throwable) {
+ writer.print(" -> ");
+ ((Throwable) value).printStackTrace(writer);
+ } else {
+ writer.print(" -> ");
+ writer.print(value);
+ }
+ }
+ writer.close();
+ return builder.toString();
+ }
+ }
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java
new file mode 100644
index 0000000..f7a4d38
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 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 Simple 6.x container classes.
+ */
+package org.glassfish.jersey.simple;
diff --git a/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..a0a4981
--- /dev/null
+++ b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.simple.SimpleContainerProvider
\ No newline at end of file
diff --git a/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties b/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties
new file mode 100644
index 0000000..138fa5b
--- /dev/null
+++ b/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2014, 2018 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
+#
+
+error.when.creating.server=IOException thrown when trying to create simple server.
+uri.cannot.be.null=The URI must not be null.
+wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL.
+wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL.
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
new file mode 100644
index 0000000..eb653ca
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.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.After;
+
+/**
+ * Abstract Simple HTTP Server unit tester.
+ *
+ * @author Paul Sandoz
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Miroslav Fuksa
+ */
+public abstract class AbstractSimpleServerTester {
+
+ public static final String CONTEXT = "";
+ private final int DEFAULT_PORT = 9998;
+
+ private static final Logger LOGGER = Logger.getLogger(AbstractSimpleServerTester.class.getName());
+
+ /**
+ * Get the port to be used for test application deployments.
+ *
+ * @return The HTTP port of the URI
+ */
+ protected 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 not positive.");
+ }
+ return i;
+ } catch (NumberFormatException e) {
+ LOGGER.log(
+ Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid positive integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+
+ return DEFAULT_PORT;
+ }
+
+ private volatile SimpleServer server;
+
+ public UriBuilder getUri() {
+ return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+ }
+
+ public void startServer(Class... resources) {
+ ResourceConfig config = new ResourceConfig(resources);
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ public URI getBaseUri() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+ }
+
+ public void setDebug(boolean enable) {
+ if (server != null) {
+ server.setDebug(enable);
+ }
+ }
+
+ public void stopServer() {
+ try {
+ server.close();
+ server = null;
+ LOGGER.log(Level.INFO, "Simple-http server stopped.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ stopServer();
+ }
+ }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java
new file mode 100644
index 0000000..9587b04
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2013, 2018 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.simple;
+
+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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.container.TimeoutHandler;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Michal Gajdos
+ */
+public class AsyncTest extends AbstractSimpleServerTester {
+
+ @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;
+
+ @Before
+ public void setUp() throws Exception {
+ startServer(AsyncResource.class);
+ client = ClientBuilder.newClient();
+ }
+
+ @Override
+ @After
+ public void tearDown() {
+ super.tearDown();
+ client = null;
+ }
+
+ @Test
+ public void testAsyncGet() throws ExecutionException, InterruptedException {
+ final Future<Response> 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
+ public void testAsyncGetWithTimeout()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ final Future<Response> 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/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java
new file mode 100644
index 0000000..15e7b28
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractSimpleServerTester {
+ @Path("{status}")
+ public static class ExceptionResource {
+ @GET
+ public String get(@PathParam("status") int status) {
+ throw new WebApplicationException(status);
+ }
+
+ }
+
+ @Test
+ public void test400StatusCode() {
+ 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/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java
new file mode 100644
index 0000000..439b7d2
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractSimpleServerTester {
+
+ @Path("/one")
+ public static class One {
+ @GET
+ public String get() {
+ return "one";
+ }
+ }
+
+ @Path("/two")
+ public static class Two {
+ @GET
+ public String get() {
+ return "two";
+ }
+ }
+
+ public static class Reloader extends AbstractContainerLifecycleListener {
+ Container container;
+
+ public void reload(ResourceConfig newConfig) {
+ container.reload(newConfig);
+ }
+
+ public void reload() {
+ container.reload();
+ }
+
+ @Override
+ public void onStartup(Container container) {
+ this.container = container;
+ }
+
+ }
+
+ @Test
+ public void testReload() {
+ final ResourceConfig rc = new ResourceConfig(One.class);
+
+ Reloader reloader = new Reloader();
+ rc.registerInstances(reloader);
+
+ startServer(rc);
+
+ WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+ // add Two resource
+ reloader.reload(new ResourceConfig(One.class, Two.class));
+
+ assertEquals("one", r.path("one").request().get(String.class));
+ assertEquals("two", r.path("two").request().get(String.class));
+ }
+
+ static class StartStopListener extends AbstractContainerLifecycleListener {
+ volatile boolean started;
+ volatile boolean stopped;
+
+ @Override
+ public void onStartup(Container container) {
+ started = true;
+ }
+
+ @Override
+ public void onShutdown(Container container) {
+ stopped = true;
+ }
+ }
+
+ @Test
+ public void testStartupShutdownHooks() {
+ final StartStopListener listener = new StartStopListener();
+
+ startServer(new ResourceConfig(One.class).register(listener));
+
+ WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ assertThat(r.path("one").request().get(String.class), equalTo("one"));
+ assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+ stopServer();
+
+ assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+ assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+ }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java
new file mode 100644
index 0000000..3b12b1a
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OptionsTest extends AbstractSimpleServerTester {
+
+ @Path("helloworld")
+ public static class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+ }
+
+ @Path("/users")
+ public class UserResource {
+
+ @Path("/current")
+ @GET
+ @Produces("text/plain")
+ public String getCurrentUser() {
+ return "current user";
+ }
+ }
+
+ private Client client;
+
+ @Before
+ public void setUp() throws Exception {
+ startServer(HelloWorldResource.class, UserResource.class);
+ client = ClientBuilder.newClient();
+ }
+
+ @Override
+ @After
+ public void tearDown() {
+ super.tearDown();
+ client = null;
+ }
+
+
+ @Test
+ public void testFooBarOptions() {
+ 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"));
+ }
+
+ @Test
+ public void testNoDefaultMethod() {
+ Response response = client.target(getUri()).path("/users").request().options();
+ assertThat(response.getStatus(), is(404));
+ }
+
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java
new file mode 100644
index 0000000..e0b6611
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests the parallel execution of multiple requests.
+ *
+ * @author Stepan Kopriva
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ */
+public class ParallelTest extends AbstractSimpleServerTester {
+
+ // Server-side dispatcher and selector pool configuration
+ private static final int selectorThreads = Runtime.getRuntime().availableProcessors();
+ private static final int dispatcherThreads = Math.max(8, selectorThreads * 2);
+
+ private static final int numberOfThreads = 100;
+
+ private static final String PATH = "test";
+ private static AtomicInteger receivedCounter = new AtomicInteger(0);
+ private static AtomicInteger resourceCounter = new AtomicInteger(0);
+ private static CountDownLatch latch = new CountDownLatch(numberOfThreads);
+
+ @Path(PATH)
+ public static class MyResource {
+
+ @GET
+ public String get() {
+ this.sleep();
+ resourceCounter.addAndGet(1);
+ return "GET";
+ }
+
+ private void sleep() {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(ParallelTest.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ private class ResourceThread extends Thread {
+
+ private WebTarget target;
+ private String path;
+
+ public ResourceThread(WebTarget target, String path) {
+ this.target = target;
+ this.path = path;
+ }
+
+ @Override
+ public void run() {
+ assertEquals("GET", target.path(path).request().get(String.class));
+ receivedCounter.addAndGet(1);
+ latch.countDown();
+ }
+ }
+
+ @Test
+ public void testParallel() {
+ ResourceConfig config = new ResourceConfig(MyResource.class);
+ startServer(config, dispatcherThreads, selectorThreads);
+ WebTarget target = ClientBuilder.newClient().target(getUri().path("/").build());
+
+ for (int i = 1; i <= numberOfThreads; i++) {
+ ResourceThread rt = new ResourceThread(target, PATH);
+ rt.start();
+ }
+
+ try {
+ latch.await(8000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(ParallelTest.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ int result = receivedCounter.get();
+ assertEquals(numberOfThreads, result);
+ }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java
new file mode 100644
index 0000000..1e2d737
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010, 2018 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.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TraceTest extends AbstractSimpleServerTester {
+
+ @Path("helloworld")
+ public static class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+ }
+
+ @Path("/users")
+ public class UserResource {
+
+ @Path("/current")
+ @GET
+ @Produces("text/plain")
+ public String getCurrentUser() {
+ return "current user";
+ }
+ }
+
+ private Client client;
+
+ @Before
+ public void setUp() throws Exception {
+ startServerNoLoggingFilter(HelloWorldResource.class, UserResource.class); // disable crude
+ // LoggingFilter
+ setDebug(true);
+ client = ClientBuilder.newClient();
+ }
+
+ @Override
+ @After
+ public void tearDown() {
+ super.tearDown();
+ client = null;
+ }
+
+
+ @Test
+ public void testFooBarOptions() {
+ for (int i = 0; i < 100; i++) {
+ 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());
+
+ try {
+ Thread.sleep(50);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void _checkAllowContent(final String content) {
+ assertTrue(content.contains("GET"));
+ assertTrue(content.contains("HEAD"));
+ assertTrue(content.contains("OPTIONS"));
+ }
+
+ @Test
+ public void testNoDefaultMethod() {
+ Response response = client.target(getUri()).path("/users").request().options();
+ assertThat(response.getStatus(), is(404));
+ }
+}