diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java b/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java index 99ac0621d4..c1ea9bc495 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java @@ -63,7 +63,6 @@ import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Application; import javax.ws.rs.core.Configuration; -import javax.ws.rs.core.GenericType; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.ext.MessageBodyReader; @@ -85,7 +84,6 @@ import org.glassfish.jersey.internal.inject.Injections; import org.glassfish.jersey.internal.inject.InstanceBinding; import org.glassfish.jersey.internal.inject.Providers; -import org.glassfish.jersey.internal.util.collection.Ref; import org.glassfish.jersey.message.MessageBodyWorkers; import org.glassfish.jersey.message.internal.MessageBodyFactory; import org.glassfish.jersey.message.internal.MessagingBinders; @@ -108,7 +106,9 @@ import org.glassfish.jersey.server.internal.monitoring.CompositeApplicationEventListener; import org.glassfish.jersey.server.internal.monitoring.MonitoringContainerListener; import org.glassfish.jersey.server.internal.process.ReferencesInitializer; +import org.glassfish.jersey.server.internal.process.RequestProcessingConfigurator; import org.glassfish.jersey.server.internal.process.RequestProcessingContext; +import org.glassfish.jersey.server.internal.process.RequestProcessingContextReference; import org.glassfish.jersey.server.internal.routing.Routing; import org.glassfish.jersey.server.model.ComponentModelValidator; import org.glassfish.jersey.server.model.ModelProcessor; @@ -292,6 +292,7 @@ private void initialize(ApplicationConfigurator applicationConfigurator, Injecti ServerBootstrapBag bootstrapBag = new ServerBootstrapBag(); bootstrapBag.setManagedObjectsFinalizer(managedObjectsFinalizer); List bootstrapConfigurators = Arrays.asList( + new RequestProcessingConfigurator(), new RequestScope.RequestScopeConfigurator(), new ParamConverterConfigurator(), new ParamExtractorConfigurator(), @@ -425,9 +426,8 @@ private ServerRuntime initialize(InjectionManager injectionManager, List> requestProcessingType = new GenericType>() {}; - ReferencesInitializer referencesInitializer = new ReferencesInitializer(injectionManager, - () -> injectionManager.getInstance(requestProcessingType.getType())); + final ReferencesInitializer referencesInitializer = new ReferencesInitializer(injectionManager, + () -> injectionManager.getInstance(RequestProcessingContextReference.class)); final Stage rootStage = Stages .chain(referencesInitializer) diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerBinder.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerBinder.java index 17e2a478f7..487f206a6a 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerBinder.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerBinder.java @@ -49,7 +49,6 @@ import org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor; import org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor; import org.glassfish.jersey.server.internal.monitoring.MonitoringContainerListener; -import org.glassfish.jersey.server.internal.process.ServerProcessingBinder; /** * Server injection binder. @@ -61,8 +60,7 @@ class ServerBinder extends AbstractBinder { @Override protected void configure() { - install(new ServerProcessingBinder(), - new MappableExceptionWrapperInterceptor.Binder(), + install(new MappableExceptionWrapperInterceptor.Binder(), new MonitoringContainerListener.Binder()); //ChunkedResponseWriter diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerBootstrapBag.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerBootstrapBag.java index d8c78507b2..190832c6ce 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerBootstrapBag.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerBootstrapBag.java @@ -47,9 +47,11 @@ import org.glassfish.jersey.internal.BootstrapBag; import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.Ref; import org.glassfish.jersey.server.internal.JerseyResourceContext; import org.glassfish.jersey.server.internal.ProcessingProviders; import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; +import org.glassfish.jersey.server.internal.process.RequestProcessingContext; import org.glassfish.jersey.server.model.ModelProcessor; import org.glassfish.jersey.server.model.ResourceMethodInvoker; import org.glassfish.jersey.server.model.ResourceModel; @@ -171,6 +173,7 @@ public void setResourceMethodInvokerBuilder(ResourceMethodInvoker.Builder resour } public ResourceModel getResourceModel() { + requireNonNull(resourceModel, ResourceModel.class); return resourceModel; } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java index cc1358648f..1de6371732 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java @@ -854,6 +854,11 @@ public void run() { }); } + @Override + public void invokeManaged(Runnable runnable) { + responder.runtime.managedAsyncExecutor.get().submit(runnable); + } + @Override public boolean suspend() { synchronized (stateLock) { diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java index 5d93aee5ab..b9af65656b 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java @@ -45,7 +45,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.function.Function; +import java.util.function.Supplier; import javax.ws.rs.BeanParam; import javax.ws.rs.CookieParam; @@ -72,6 +72,7 @@ import org.glassfish.jersey.server.ServerBootstrapBag; import org.glassfish.jersey.server.Uri; import org.glassfish.jersey.server.internal.process.AsyncContext; +import org.glassfish.jersey.server.internal.process.RequestProcessingContextReference; import org.glassfish.jersey.server.spi.internal.ValueParamProvider; /** @@ -85,17 +86,17 @@ public class ValueParamProviderConfigurator implements BootstrapConfigurator { public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { ServerBootstrapBag serverBag = (ServerBootstrapBag) bootstrapBag; - Provider asyncContextProvider = () -> injectionManager.getInstance(AsyncContext.class); - - Function, Configuration> clientConfigProvider = - clientConfigClass -> Injections.getOrCreate(injectionManager, clientConfigClass); - - LazyValue lazyConfiguration = - Values.lazy((Value) () -> injectionManager.getInstance(Configuration.class)); + // Provide request scoped AsyncContext without the proxy. + Provider asyncContextProvider = () -> { + RequestProcessingContextReference reference = injectionManager.getInstance(RequestProcessingContextReference.class); + return reference.get().asyncContext(); + }; + // Provide ContextInjectionResolver that is implemented by Injection Provider. LazyValue lazyContextResolver = Values.lazy((Value) () -> injectionManager.getInstance(ContextInjectionResolver.class)); + Supplier configuration = serverBag::getConfiguration; Provider paramExtractor = serverBag::getMultivaluedParameterExtractorProvider; // Parameter injection value providers @@ -128,8 +129,8 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { BeanParamValueParamProvider beanProvider = new BeanParamValueParamProvider(paramExtractor, injectionManager); suppliers.add(beanProvider); - WebTargetValueParamProvider webTargetProvider = - new WebTargetValueParamProvider(lazyConfiguration, clientConfigProvider); + WebTargetValueParamProvider webTargetProvider = new WebTargetValueParamProvider(configuration, + clientConfigClass -> Injections.getOrCreate(injectionManager, clientConfigClass)); suppliers.add(webTargetProvider); DelegatedInjectionValueParamProvider contextProvider = @@ -151,7 +152,12 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { injectionManager.register(Bindings.service(entityProvider).to(ValueParamProvider.class)); injectionManager.register(Bindings.service(contextProvider).to(ValueParamProvider.class)); - Provider request = () -> injectionManager.getInstance(ContainerRequest.class); + // Provide request scoped ContainerRequest without the proxy. + Provider request = () -> { + RequestProcessingContextReference reference = injectionManager.getInstance(RequestProcessingContextReference.class); + return reference.get().request(); + }; + registerResolver(injectionManager, asyncProvider, Suspended.class, request); registerResolver(injectionManager, cookieProvider, CookieParam.class, request); registerResolver(injectionManager, formProvider, FormParam.class, request); @@ -163,7 +169,7 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { registerResolver(injectionManager, beanProvider, BeanParam.class, request); } - private void registerResolver(InjectionManager im, ValueParamProvider vfp, Class annotation, + private void registerResolver(InjectionManager im, ValueParamProvider vfp, Class annotation, Provider request) { im.register(Bindings.injectionResolver(new ParamInjectionResolver<>(vfp, annotation, request))); } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/WebTargetValueParamProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/WebTargetValueParamProvider.java index 3930ab73e6..ef5e5caef3 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/WebTargetValueParamProvider.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/WebTargetValueParamProvider.java @@ -49,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.ws.rs.ProcessingException; @@ -63,7 +64,6 @@ import org.glassfish.jersey.internal.util.Producer; import org.glassfish.jersey.internal.util.PropertiesHelper; import org.glassfish.jersey.internal.util.ReflectionHelper; -import org.glassfish.jersey.internal.util.collection.LazyValue; import org.glassfish.jersey.internal.util.collection.Value; import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.server.ClientBinding; @@ -82,7 +82,7 @@ final class WebTargetValueParamProvider extends AbstractValueParamProvider { private final Function, Configuration> clientConfigProvider; - private final LazyValue serverConfig; + private final Supplier serverConfig; private final ConcurrentMap> managedClients; private static class ManagedClient { @@ -276,7 +276,7 @@ public WebTarget apply(ContainerRequest containerRequest) { * @param serverConfig server-side serverConfig. * @param clientConfigProvider function which get or create a new client serverConfig according to provided class. */ - public WebTargetValueParamProvider(LazyValue serverConfig, + public WebTargetValueParamProvider(Supplier serverConfig, Function, Configuration> clientConfigProvider) { super(null, Parameter.Source.URI); this.clientConfigProvider = clientConfigProvider; diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/AsyncContext.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/AsyncContext.java index ae69599f9a..c5d981eb9c 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/AsyncContext.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/AsyncContext.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2012-2014 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -97,4 +97,11 @@ public static enum State { * @param producer response producer. */ public void invokeManaged(Producer producer); + + /** + * Invoke the provided runnable in a Jersey-managed asynchronous thread. + * + * @param runnable to be invoked. + */ + public void invokeManaged(Runnable runnable); } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ReferencesInitializer.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ReferencesInitializer.java index 3bcff118cd..21710ab8b1 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ReferencesInitializer.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ReferencesInitializer.java @@ -45,7 +45,6 @@ import javax.inject.Provider; import org.glassfish.jersey.internal.inject.InjectionManager; -import org.glassfish.jersey.internal.util.collection.Ref; import org.glassfish.jersey.server.spi.RequestScopedInitializer; /** @@ -56,7 +55,7 @@ public final class ReferencesInitializer implements Function { private final InjectionManager injectionManager; - private final Provider> processingContextRefProvider; + private final Provider processingContextRefProvider; /** * Injection constructor. @@ -65,7 +64,7 @@ public final class ReferencesInitializer implements Function> processingContextRefProvider) { + InjectionManager injectionManager, Provider processingContextRefProvider) { this.injectionManager = injectionManager; this.processingContextRefProvider = processingContextRefProvider; } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingConfigurator.java new file mode 100644 index 0000000000..f792794123 --- /dev/null +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingConfigurator.java @@ -0,0 +1,193 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package org.glassfish.jersey.server.internal.process; + +import java.util.function.Supplier; + +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; + +import javax.inject.Inject; + +import org.glassfish.jersey.internal.BootstrapBag; +import org.glassfish.jersey.internal.BootstrapConfigurator; +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.process.internal.RequestScoped; +import org.glassfish.jersey.server.CloseableService; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.internal.routing.UriRoutingContext; +import org.glassfish.jersey.server.spi.ExternalRequestScope; + +/** + * Configurator which initializes and register {@link ExternalRequestScope} instance into {@link InjectionManager}. + * + * @author Petr Bouda (petr.bouda at oracle.com) + */ +public class RequestProcessingConfigurator implements BootstrapConfigurator { + + @Override + public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { + injectionManager.register(new ServerProcessingBinder()); + } + + private static class ContainerRequestFactory implements Supplier { + + private final RequestProcessingContextReference reference; + + @Inject + private ContainerRequestFactory(RequestProcessingContextReference reference) { + this.reference = reference; + } + + @Override + public ContainerRequest get() { + return reference.get().request(); + } + } + + private static class UriRoutingContextFactory implements Supplier { + + private final RequestProcessingContextReference reference; + + @Inject + private UriRoutingContextFactory(RequestProcessingContextReference reference) { + this.reference = reference; + } + + @Override + public UriRoutingContext get() { + return reference.get().uriRoutingContext(); + } + } + + private static class CloseableServiceFactory implements Supplier { + + private final RequestProcessingContextReference reference; + + @Inject + private CloseableServiceFactory(RequestProcessingContextReference reference) { + this.reference = reference; + } + + @Override + public CloseableService get() { + return reference.get().closeableService(); + } + } + + private static class AsyncContextFactory implements Supplier { + + private final RequestProcessingContextReference reference; + + @Inject + private AsyncContextFactory(RequestProcessingContextReference reference) { + this.reference = reference; + } + + @Override + public AsyncContext get() { + return reference.get().asyncContext(); + } + } + + /** + * Defines server-side request processing injection bindings. + * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + private class ServerProcessingBinder extends AbstractBinder { + + @Override + protected void configure() { + bindAsContract(RequestProcessingContextReference.class) + .in(RequestScoped.class); + + // Bind non-proxiable ContainerRequest injection injection points + bindFactory(ContainerRequestFactory.class) + .to(ContainerRequest.class).to(ContainerRequestContext.class) + .proxy(false) + .in(RequestScoped.class); + + // Bind proxiable HttpHeaders, Request and ContainerRequestContext injection injection points + bindFactory(ContainerRequestFactory.class) + .to(HttpHeaders.class).to(Request.class) + .proxy(true).proxyForSameScope(false) + .in(RequestScoped.class); + + // Bind proxiable UriInfo, ExtendedUriInfo and ResourceInfo injection points + bindFactory(UriRoutingContextFactory.class) + .to(UriInfo.class).to(ExtendedUriInfo.class).to(ResourceInfo.class) + .proxy(true).proxyForSameScope(false) + .in(RequestScoped.class); + + // Bind proxiable SecurityContext injection point. + // NOTE: + // SecurityContext must be injected using the Injectee. The reason is that SecurityContext can be changed by filters, + // but the proxy internally caches the first SecurityContext value injected in the RequestScope. This is prevented by + // using SecurityContextInjectee that does not cache the SecurityContext instances and instead delegates calls to + // the SecurityContext instance retrieved from current ContainerRequestContext. + bind(SecurityContextInjectee.class) + .to(SecurityContext.class) + .proxy(true).proxyForSameScope(false) + .in(RequestScoped.class); + + // Bind proxiable CloseableService injection point. + bindFactory(CloseableServiceFactory.class) + .to(CloseableService.class) + .proxy(true).proxyForSameScope(false) + .in(RequestScoped.class); + + // Bind proxiable AsyncContext and AsyncResponse injection points. + // TODO maybe we can get rid of these completely? Or at least for AsyncContext? + bindFactory(AsyncContextFactory.class) + .to(AsyncContext.class) + .to(AsyncResponse.class) + .in(RequestScoped.class); + } + } +} diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContext.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContext.java index eaf6d6ae2a..680645e369 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContext.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContext.java @@ -130,7 +130,7 @@ public RoutingContext routingContext() { * Get the underlying {@link UriRoutingContext} instance for the processed * container request. *

- * This instance is used by {@link ServerProcessingBinder} to satisfy injection of multiple types, namely: + * This instance is used by {@link RequestProcessingConfigurator} to satisfy injection of multiple types, namely: *

    *
  • {@link javax.ws.rs.core.UriInfo}
  • *
  • {@link org.glassfish.jersey.server.ExtendedUriInfo}
  • diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ReferenceTransformingFactory.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContextReference.java similarity index 51% rename from core-common/src/main/java/org/glassfish/jersey/internal/inject/ReferenceTransformingFactory.java rename to core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContextReference.java index 423b7449e6..d6122375bf 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ReferenceTransformingFactory.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/RequestProcessingContextReference.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2014-2017 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -37,58 +37,31 @@ * only if the new code is made subject to such option by the copyright * holder. */ -package org.glassfish.jersey.internal.inject; -import java.util.function.Supplier; - -import javax.inject.Provider; +package org.glassfish.jersey.server.internal.process; import org.glassfish.jersey.internal.util.collection.Ref; +import org.glassfish.jersey.process.internal.RequestScoped; /** - * An abstract injection factory that provides injection of an instance of target type {@code T} - * by {@link org.glassfish.jersey.internal.inject.ReferenceTransformingFactory.Transformer transforming} - * a value of injected source reference {@link org.glassfish.jersey.internal.util.collection.Ref Ref<S>}. - * - * @author Marek Potociar (marek.potociar at oracle.com) + * Wrapper that holds the reference of the {@link RequestProcessingContext}. This class helps to get the current request scoped + * object without wrapping using the proxy. Outer wrapper can be proxied but inner reference object still remains the direct + * reference. * - * @param the type of the injected source type {@link Ref reference}. - * @param the type of provided entity. + * @author Petr Bouda (petr.bouda at oracle.com) */ -public abstract class ReferenceTransformingFactory implements Supplier { - /** - * Transforming function responsible for transforming an instance of source type {@code S} into an instance of - * target type {@code T}. - * - * @param source type. - * @param target type. - */ - public interface Transformer { - /** - * Transform an instance of source type into an instance of target type. - * - * @param value instance of source type. - * @return instance of target type. - */ - T transform(S value); - } +@RequestScoped +public class RequestProcessingContextReference implements Ref { - private final Provider> refProvider; - private final Transformer transformer; + private RequestProcessingContext processingContext; - /** - * Initialize reference transforming factory. - * - * @param refProvider source type reference provider. - * @param transformer source to target type transforming function. - */ - protected ReferenceTransformingFactory(final Provider> refProvider, final Transformer transformer) { - this.refProvider = refProvider; - this.transformer = transformer; + @Override + public void set(RequestProcessingContext processingContext) { + this.processingContext = processingContext; } @Override - public T get() { - return transformer.transform(refProvider.get().get()); + public RequestProcessingContext get() { + return processingContext; } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ServerProcessingBinder.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ServerProcessingBinder.java deleted file mode 100644 index c1838b1268..0000000000 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/process/ServerProcessingBinder.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2014-2017 Oracle and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * http://glassfish.java.net/public/CDDL+GPL_1_1.html - * or packager/legal/LICENSE.txt. See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at packager/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * Oracle designates this particular file as subject to the "Classpath" - * exception as provided by Oracle in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ - -package org.glassfish.jersey.server.internal.process; - -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriInfo; - -import javax.inject.Inject; -import javax.inject.Provider; - -import org.glassfish.jersey.internal.inject.AbstractBinder; -import org.glassfish.jersey.internal.inject.ReferenceTransformingFactory; -import org.glassfish.jersey.internal.inject.ReferencingFactory; -import org.glassfish.jersey.internal.util.collection.Ref; -import org.glassfish.jersey.process.internal.RequestScoped; -import org.glassfish.jersey.server.CloseableService; -import org.glassfish.jersey.server.ContainerRequest; -import org.glassfish.jersey.server.ExtendedUriInfo; -import org.glassfish.jersey.server.internal.routing.UriRoutingContext; - -/** - * Defines server-side request processing injection bindings. - * - * @author Marek Potociar (marek.potociar at oracle.com) - */ -public class ServerProcessingBinder extends AbstractBinder { - - @Override - protected void configure() { - // Bind non-proxiable Ref injection point - bindFactory(ReferencingFactory.referenceFactory()) - .to(new GenericType>() { }) - .proxy(false) - .in(RequestScoped.class); - - // Bind non-proxiable ContainerRequest injection injection points - bindFactory(ContainerRequestFactory.class) - .to(ContainerRequest.class).to(ContainerRequestContext.class) - .proxy(false) - .in(RequestScoped.class); - - // Bind proxiable HttpHeaders, Request and ContainerRequestContext injection injection points - bindFactory(ContainerRequestFactory.class) - .to(HttpHeaders.class).to(Request.class) - .proxy(true).proxyForSameScope(false) - .in(RequestScoped.class); - - // Bind proxiable UriInfo, ExtendedUriInfo and ResourceInfo injection points - bindFactory(UriRoutingContextFactory.class) - .to(UriInfo.class).to(ExtendedUriInfo.class).to(ResourceInfo.class) - .proxy(true).proxyForSameScope(false) - .in(RequestScoped.class); - - // Bind proxiable SecurityContext injection point. - // NOTE: - // SecurityContext must be injected using the Injectee. The reason is that SecurityContext can be changed by filters, - // but the proxy internally caches the first SecurityContext value injected in the RequestScope. This is prevented by - // using SecurityContextInjectee that does not cache the SecurityContext instances and instead delegates calls to - // the SecurityContext instance retrieved from current ContainerRequestContext. - bind(SecurityContextInjectee.class) - .to(SecurityContext.class) - .proxy(true).proxyForSameScope(false) - .in(RequestScoped.class); - - // Bind proxiable CloseableService injection point. - bindFactory(CloseableServiceFactory.class) - .to(CloseableService.class) - .proxy(true).proxyForSameScope(false) - .in(RequestScoped.class); - - // Bind proxiable AsyncContext and AsyncResponse injection points. - // TODO maybe we can get rid of these completely? Or at least for AsyncContext? - bindFactory(AsyncContextFactory.class) - .to(AsyncContext.class) - .to(AsyncResponse.class) - .in(RequestScoped.class); - - // Bind request-scoped references initializer. - bindAsContract(ReferencesInitializer.class); - } - - private static class ContainerRequestFactory - extends ReferenceTransformingFactory { - - @Inject - protected ContainerRequestFactory(final Provider> refProvider) { - super(refProvider, RequestProcessingContext::request); - } - - @Override - @RequestScoped - public ContainerRequest get() { - return super.get(); - } - } - - private static class UriRoutingContextFactory - extends ReferenceTransformingFactory { - - @Inject - protected UriRoutingContextFactory(final Provider> refProvider) { - super(refProvider, RequestProcessingContext::uriRoutingContext); - } - - @Override - @RequestScoped - public UriRoutingContext get() { - return super.get(); - } - } - - private static class CloseableServiceFactory - extends ReferenceTransformingFactory { - - @Inject - protected CloseableServiceFactory(final Provider> refProvider) { - super(refProvider, RequestProcessingContext::closeableService); - } - - @Override - @RequestScoped - public CloseableService get() { - return super.get(); - } - } - - private static class AsyncContextFactory - extends ReferenceTransformingFactory { - - @Inject - protected AsyncContextFactory(final Provider> refProvider) { - super(refProvider, RequestProcessingContext::asyncContext); - } - - @Override - @RequestScoped - public AsyncContext get() { - return super.get(); - } - } -} diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java index e55bd38f0b..d30b6060cb 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/model/ResourceMethodInvoker.java @@ -52,6 +52,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -114,7 +118,7 @@ public class ResourceMethodInvoker implements Endpoint, ResourceInfo { /** * Resource method invoker helper. - * + *

    * The builder API provides means for constructing a properly initialized * {@link ResourceMethodInvoker resource method invoker} instances. */ @@ -409,10 +413,46 @@ public ContainerResponse apply(final RequestProcessingContext processingContext) return null; // return null on current thread } else { // TODO replace with processing context factory method. - return new ContainerResponse(request, invoke(processingContext, resource)); + Response response = invoke(processingContext, resource); + + if (response.hasEntity()) { + Object entityFuture = response.getEntity(); + if (entityFuture instanceof CompletionStage) { + CompletableFuture completableFuture = ((CompletionStage) entityFuture).toCompletableFuture(); + + // suspend - we know that this feature is not done, see AbstractJavaResourceMethodDispatcher#invoke + if (!processingContext.asyncContext().suspend()) { + throw new ProcessingException(LocalizationMessages.ERROR_SUSPENDING_ASYNC_REQUEST()); + } + + // wait for a response + completableFuture.whenCompleteAsync( + whenComplete(processingContext), + command -> processingContext.asyncContext().invokeManaged(command)); + + return null; // return null on the current thread + } + } + + return new ContainerResponse(request, response); } } + private BiConsumer whenComplete(RequestProcessingContext processingContext) { + return (entity, exception) -> { + + if (exception != null) { + if (exception instanceof CancellationException) { + processingContext.asyncContext().resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()); + } else { + processingContext.asyncContext().resume(((Throwable) exception)); + } + } else { + processingContext.asyncContext().resume(entity); + } + }; + } + private Response invoke(final RequestProcessingContext context, final Object resource) { Response jaxrsResponse; diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/AbstractJavaResourceMethodDispatcher.java b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/AbstractJavaResourceMethodDispatcher.java index 36ec79b2c2..08a8e442fa 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/AbstractJavaResourceMethodDispatcher.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/AbstractJavaResourceMethodDispatcher.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2011-2015 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011-2017 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -44,6 +44,9 @@ import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.security.PrivilegedAction; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import javax.ws.rs.ProcessingException; import javax.ws.rs.WebApplicationException; @@ -141,7 +144,26 @@ public Object run() { final long timestamp = tracingLogger.timestamp(ServerTraceEvent.METHOD_INVOKE); try { - return methodHandler.invoke(resource, method, args); + Object result = methodHandler.invoke(resource, method, args); + + // if a response is a CompletionStage and is done, we don't need to suspend and resume + if (result instanceof CompletionStage) { + CompletableFuture resultFuture = ((CompletionStage) result).toCompletableFuture(); + + if (resultFuture.isDone()) { + if (resultFuture.isCancelled()) { + return Response.status(Response.Status.SERVICE_UNAVAILABLE).build(); + } else { + try { + return resultFuture.get(); + } catch (ExecutionException e) { + throw new InvocationTargetException(e.getCause()); + } + } + } + } + + return result; } catch (IllegalAccessException | IllegalArgumentException | UndeclaredThrowableException ex) { throw new ProcessingException(LocalizationMessages.ERROR_RESOURCE_JAVA_METHOD_INVOCATION(), ex); diff --git a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java index e1c5ffcd1b..3cdc6658b0 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/model/internal/JavaResourceMethodDispatcherProvider.java @@ -209,8 +209,6 @@ protected Response doDispatch(final Object resource, final ContainerRequest cont if (o instanceof Response) { return Response.class.cast(o); -// } else if (o instanceof JResponse) { -// context.getResponseContext().setResponse(((JResponse)o).toResponse()); } else if (o != null) { return Response.ok().entity(o).build(); } else { @@ -236,12 +234,10 @@ private static final class TypeOutInvoker extends AbstractMethodParamInvoker { protected Response doDispatch(final Object resource, final ContainerRequest containerRequest) throws ProcessingException { final Object o = invoke(containerRequest, resource, getParamValues(containerRequest)); if (o != null) { - - Response response = Response.ok().entity(o).build(); - // TODO set the method return Java type to the proper context. -// Response r = new ResponseBuilderImpl(). -// entityWithType(o, t).status(200).build(); - return response; + if (o instanceof Response) { + return Response.class.cast(o); + } + return Response.ok().entity(o).build(); } else { return Response.noContent().build(); } diff --git a/core-server/src/test/java/org/glassfish/jersey/server/TestInjectionManagerFactory.java b/core-server/src/test/java/org/glassfish/jersey/server/TestInjectionManagerFactory.java index 53d61965e0..55eafed2af 100644 --- a/core-server/src/test/java/org/glassfish/jersey/server/TestInjectionManagerFactory.java +++ b/core-server/src/test/java/org/glassfish/jersey/server/TestInjectionManagerFactory.java @@ -60,6 +60,7 @@ import org.glassfish.jersey.server.internal.inject.ParamConverterConfigurator; import org.glassfish.jersey.server.internal.inject.ParamExtractorConfigurator; import org.glassfish.jersey.server.internal.inject.ValueParamProviderConfigurator; +import org.glassfish.jersey.server.internal.process.RequestProcessingConfigurator; import org.glassfish.jersey.server.model.internal.ResourceMethodInvokerConfigurator; /** @@ -116,6 +117,7 @@ public static BootstrapResult createInjectionManager(ResourceConfig runtimeConfi bootstrapBag.setManagedObjectsFinalizer(managedObjectsFinalizer); List bootstrapConfigurators = Arrays.asList( + new RequestProcessingConfigurator(), new RequestScope.RequestScopeConfigurator(), new ParamConverterConfigurator(), new ParamExtractorConfigurator(), diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java new file mode 100644 index 0000000000..801a79eb0a --- /dev/null +++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/CompletionStageTest.java @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package org.glassfish.jersey.tests.e2e.server; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author Pavel Bucek (pavel.bucek at oracle.com) + */ +public class CompletionStageTest extends JerseyTest { + + static final String ENTITY = "entity"; + // delay of async operations in seconds. + static final int DELAY = 1; + + @Override + protected Application configure() { + return new ResourceConfig(CompletionStageResource.class); + } + + @Test + public void testGetCompleted() { + Response response = target("cs/completed").request().get(); + + assertThat(response.getStatus(), is(200)); + assertThat(response.readEntity(String.class), is(ENTITY)); + } + + @Test + public void testGetException400() { + Response response = target("cs/exception400").request().get(); + + assertThat(response.getStatus(), is(400)); + } + + @Test + public void testGetException405() { + Response response = target("cs/exception405").request().get(); + + assertThat(response.getStatus(), is(405)); + } + + @Test + public void testGetCancelled() { + Response response = target("cs/cancelled").request().get(); + + assertThat(response.getStatus(), is(503)); + } + + @Test + public void testGetCompletedAsync() { + Response response = target("cs/completedAsync").request().get(); + + assertThat(response.getStatus(), is(200)); + assertThat(response.readEntity(String.class), is(ENTITY)); + } + + @Test + public void testGetException400Async() { + Response response = target("cs/exception400Async").request().get(); + + assertThat(response.getStatus(), is(400)); + } + + @Test + public void testGetException405Async() { + Response response = target("cs/exception405Async").request().get(); + + assertThat(response.getStatus(), is(405)); + } + + @Test + public void testGetCancelledAsync() { + Response response = target("cs/cancelledAsync").request().get(); + + assertThat(response.getStatus(), is(503)); + } + + @Path("/cs") + public static class CompletionStageResource { + + private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); + + @GET + @Path("/completed") + public CompletionStage getCompleted() { + return CompletableFuture.completedFuture(ENTITY); + } + + @GET + @Path("/exception400") + public CompletionStage getException400() { + CompletableFuture cs = new CompletableFuture<>(); + cs.completeExceptionally(new WebApplicationException(400)); + + return cs; + } + + @GET + @Path("/exception405") + public CompletionStage getException405() { + CompletableFuture cs = new CompletableFuture<>(); + cs.completeExceptionally(new WebApplicationException(405)); + + return cs; + } + + @GET + @Path("/cancelled") + public CompletionStage getCancelled() { + CompletableFuture cs = new CompletableFuture<>(); + cs.cancel(true); + + return cs; + } + + @GET + @Path("/completedAsync") + public CompletionStage getCompletedAsync() { + CompletableFuture cs = new CompletableFuture<>(); + delaySubmit(() -> cs.complete(ENTITY)); + return cs; + } + + @GET + @Path("/exception400Async") + public CompletionStage getException400Async() { + CompletableFuture cs = new CompletableFuture<>(); + delaySubmit(() -> cs.completeExceptionally(new WebApplicationException(400))); + + return cs; + } + + @GET + @Path("/exception405Async") + public CompletionStage getException405Async() { + CompletableFuture cs = new CompletableFuture<>(); + delaySubmit(() -> cs.completeExceptionally(new WebApplicationException(405))); + + return cs; + } + + @GET + @Path("/cancelledAsync") + public CompletionStage getCancelledAsync() { + CompletableFuture cs = new CompletableFuture<>(); + delaySubmit(() -> cs.cancel(true)); + + return cs; + } + + private void delaySubmit(Runnable runnable) { + EXECUTOR_SERVICE.submit(() -> { + try { + Thread.sleep(DELAY * 1000); + } catch (InterruptedException e) { + // ignore + } + + runnable.run(); + }); + } + } +}