Skip to content

Commit

Permalink
Enable CompletionStage unwrap in MBW
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Supol <[email protected]>
  • Loading branch information
jansupol committed Dec 7, 2020
1 parent fad4061 commit 21e8faf
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
import org.glassfish.jersey.internal.util.ExceptionUtils;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.PropertiesResolver;
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.message.MessageBodyWorkers;
import org.glassfish.jersey.message.internal.HeaderUtils;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
Expand All @@ -58,7 +61,8 @@
*
* @author Marek Potociar
*/
public class ClientRequest extends OutboundMessageContext implements ClientRequestContext, HttpHeaders, InjectionManagerSupplier {
public class ClientRequest extends OutboundMessageContext
implements ClientRequestContext, HttpHeaders, InjectionManagerSupplier, PropertiesResolver {

// Request-scoped configuration instance
private final ClientConfig clientConfig;
Expand All @@ -82,6 +86,10 @@ public class ClientRequest extends OutboundMessageContext implements ClientReque
private Iterable<ReaderInterceptor> readerInterceptors;
// do not add user-agent header (if not directly set) to the request.
private boolean ignoreUserAgent;
// lazy PropertiesResolver
private LazyValue<PropertiesResolver> propertiesResolver = Values.lazy(
(Value<PropertiesResolver>) () -> PropertiesResolver.create(getConfiguration(), getPropertiesDelegate())
);

private static final Logger LOGGER = Logger.getLogger(ClientRequest.class.getName());

Expand Down Expand Up @@ -120,65 +128,14 @@ public ClientRequest(final ClientRequest original) {
this.ignoreUserAgent = original.ignoreUserAgent;
}

/**
* Resolve a property value for the specified property {@code name}.
*
* <p>
* The method returns the value of the property registered in the request-specific
* property bag, if available. If no property for the given property name is found
* in the request-specific property bag, the method looks at the properties stored
* in the {@link #getConfiguration() global client-runtime configuration} this request
* belongs to. If there is a value defined in the client-runtime configuration,
* it is returned, otherwise the method returns {@code null} if no such property is
* registered neither in the client runtime nor in the request-specific property bag.
* </p>
*
* @param name property name.
* @param type expected property class type.
* @param <T> property Java type.
* @return resolved property value or {@code null} if no such property is registered.
*/
@Override
public <T> T resolveProperty(final String name, final Class<T> type) {
return resolveProperty(name, null, type);
return propertiesResolver.get().resolveProperty(name, type);
}

/**
* Resolve a property value for the specified property {@code name}.
*
* <p>
* The method returns the value of the property registered in the request-specific
* property bag, if available. If no property for the given property name is found
* in the request-specific property bag, the method looks at the properties stored
* in the {@link #getConfiguration() global client-runtime configuration} this request
* belongs to. If there is a value defined in the client-runtime configuration,
* it is returned, otherwise the method returns {@code defaultValue} if no such property is
* registered neither in the client runtime nor in the request-specific property bag.
* </p>
*
* @param name property name.
* @param defaultValue default value to return if the property is not registered.
* @param <T> property Java type.
* @return resolved property value or {@code defaultValue} if no such property is registered.
*/
@SuppressWarnings("unchecked")
@Override
public <T> T resolveProperty(final String name, final T defaultValue) {
return resolveProperty(name, defaultValue, (Class<T>) defaultValue.getClass());
}

private <T> T resolveProperty(final String name, Object defaultValue, final Class<T> type) {
// Check runtime configuration first
Object result = getConfiguration().getProperty(name);
if (result != null) {
defaultValue = result;
}

// Check request properties next
result = propertiesDelegate.getProperty(name);
if (result == null) {
result = defaultValue;
}

return (result == null) ? null : PropertiesHelper.convertValue(result, type);
return propertiesResolver.get().resolveProperty(name, defaultValue);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2020 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.internal;

import org.glassfish.jersey.internal.util.PropertiesHelper;

import javax.ws.rs.core.Configuration;

/**
* Resolver of a property value for the specified property {@code name} from the
* request-specific property bag, or the {@link Configuration global runtime configuration}.
*/
public interface PropertiesResolver {
/**
* Resolve a property value for the specified property {@code name}.
*
* <p>
* The method returns the value of the property registered in the request-specific
* property bag, if available. If no property for the given property name is found
* in the request-specific property bag, the method looks at the properties stored
* in the {@link Configuration global runtime configuration} this request
* belongs to. If there is a value defined in the runtime configuration,
* it is returned, otherwise the method returns {@code null} if no such property is
* registered neither in the runtime nor in the request-specific property bag.
* </p>
*
* @param name property name.
* @param type expected property class type.
* @param <T> property Java type.
* @return resolved property value or {@code null} if no such property is registered.
*/
public <T> T resolveProperty(final String name, final Class<T> type);

/**
* Resolve a property value for the specified property {@code name}.
*
* <p>
* The method returns the value of the property registered in the request-specific
* property bag, if available. If no property for the given property name is found
* in the request-specific property bag, the method looks at the properties stored
* in the {@link Configuration global runtime configuration} this request
* belongs to. If there is a value defined in the runtime configuration,
* it is returned, otherwise the method returns {@code defaultValue} if no such property is
* registered neither in the runtime nor in the request-specific property bag.
* </p>
*
* @param name property name.
* @param defaultValue default value to return if the property is not registered.
* @param <T> property Java type.
* @return resolved property value or {@code defaultValue} if no such property is registered.
*/
public <T> T resolveProperty(final String name, final T defaultValue);

/**
* Return new instance of {@link PropertiesResolver}.
* @param configuration Runtime {@link Configuration}.
* @param delegate Request scoped {@link PropertiesDelegate properties delegate}.
* @return A new instance of {@link PropertiesResolver}.
*/
public static PropertiesResolver create(Configuration configuration, PropertiesDelegate delegate) {
return new PropertiesResolver() {
@Override
public <T> T resolveProperty(String name, Class<T> type) {
return resolveProperty(name, null, type);
}

@Override
@SuppressWarnings("unchecked")
public <T> T resolveProperty(String name, T defaultValue) {
return resolveProperty(name, defaultValue, (Class<T>) defaultValue.getClass());
}

private <T> T resolveProperty(final String name, Object defaultValue, final Class<T> type) {
// Check runtime configuration first
Object result = configuration.getProperty(name);
if (result != null) {
defaultValue = result;
}

// Check request properties next
result = delegate.getProperty(name);
if (result == null) {
result = defaultValue;
}

return (result == null) ? null : PropertiesHelper.convertValue(result, type);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020 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
Expand Down Expand Up @@ -49,8 +49,12 @@

import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.PropertiesResolver;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.message.internal.AcceptableMediaType;
import org.glassfish.jersey.message.internal.HttpHeaderReader;
import org.glassfish.jersey.message.internal.InboundMessageContext;
Expand Down Expand Up @@ -80,7 +84,7 @@
* @author Marek Potociar
*/
public class ContainerRequest extends InboundMessageContext
implements ContainerRequestContext, Request, HttpHeaders, PropertiesDelegate {
implements ContainerRequestContext, Request, HttpHeaders, PropertiesDelegate, PropertiesResolver {

private static final URI DEFAULT_BASE_URI = URI.create("/");

Expand Down Expand Up @@ -114,6 +118,10 @@ public class ContainerRequest extends InboundMessageContext
private ContainerResponseWriter responseWriter;
// True if the request is used in the response processing phase (for example in ContainerResponseFilter)
private boolean inResponseProcessingPhase;
// lazy PropertiesResolver
private LazyValue<PropertiesResolver> propertiesResolver = Values.lazy(
(Value<PropertiesResolver>) () -> PropertiesResolver.create(getConfiguration(), getPropertiesDelegate())
);

private static final String ERROR_REQUEST_SET_ENTITY_STREAM_IN_RESPONSE_PHASE =
LocalizationMessages.ERROR_REQUEST_SET_ENTITY_STREAM_IN_RESPONSE_PHASE();
Expand Down Expand Up @@ -274,6 +282,16 @@ public <T> T readEntity(final Class<T> rawType, final Type type, final Annotatio
return super.readEntity(rawType, type, annotations, propertiesDelegate);
}

@Override
public <T> T resolveProperty(final String name, final Class<T> type) {
return propertiesResolver.get().resolveProperty(name, type);
}

@Override
public <T> T resolveProperty(final String name, final T defaultValue) {
return propertiesResolver.get().resolveProperty(name, defaultValue);
}

@Override
public Object getProperty(final String name) {
return propertiesDelegate.getProperty(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020 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
Expand Down Expand Up @@ -724,6 +724,23 @@ public final class ServerProperties {
public static final String LOCATION_HEADER_RELATIVE_URI_RESOLUTION_DISABLED =
"jersey.config.server.headers.location.relative.resolution.disabled";


/**
* If {@code true} message body writer will not use {@code CompletionStage} as a generic type.
* The {@code CompletionStage} value will be unwrapped and the message body writer will be invoked with the unwrapped type.
*
* <p>
* The default value is {@code false}.
* </p>
* <p>
* The name of the configuration property is <tt>{@value}</tt>.
* </p>
*
* @since 2.33
*/
public static final String UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE =
"jersey.config.server.unwrap.completion.stage.writer.enable";

private ServerProperties() {
// prevents instantiation
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2020 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
Expand Down Expand Up @@ -59,6 +59,7 @@
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.ProcessingProviders;
import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
Expand All @@ -83,6 +84,8 @@ public class ResourceMethodInvoker implements Endpoint, ResourceInfo {
private final Annotation[] methodAnnotations;
private final Type invocableResponseType;
private final boolean canUseInvocableResponseType;
private final boolean isCompletionStageResponseType;
private final Type completionStageResponseType;
private final ResourceMethodDispatcher dispatcher;
private final Method resourceMethod;
private final Class<?> resourceClass;
Expand Down Expand Up @@ -306,7 +309,10 @@ protected void configure() {
&& Void.class != invocableResponseType
&& // Do NOT change the entity type for Response or it's subclasses.
!((invocableResponseType instanceof Class) && Response.class.isAssignableFrom((Class) invocableResponseType));

this.isCompletionStageResponseType = ParameterizedType.class.isInstance(invocableResponseType)
&& CompletionStage.class.isAssignableFrom((Class<?>) ((ParameterizedType) invocableResponseType).getRawType());
this.completionStageResponseType =
isCompletionStageResponseType ? ((ParameterizedType) invocableResponseType).getActualTypeArguments()[0] : null;
}

private <T> void addNameBoundProviders(
Expand Down Expand Up @@ -459,7 +465,7 @@ private Response invoke(final RequestProcessingContext context, final Object res
if (canUseInvocableResponseType
&& response.hasEntity()
&& !(response.getEntityType() instanceof ParameterizedType)) {
response.setEntityType(invocableResponseType);
response.setEntityType(unwrapInvocableResponseType(context.request()));
}

return response;
Expand All @@ -478,6 +484,14 @@ private Response invoke(final RequestProcessingContext context, final Object res
return jaxrsResponse;
}

private Type unwrapInvocableResponseType(ContainerRequest request) {
if (isCompletionStageResponseType
&& request.resolveProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.FALSE)) {
return completionStageResponseType;
}
return invocableResponseType;
}

/**
* Get all bound request filters applicable to the {@link #getResourceMethod() resource method}
* wrapped by this invoker.
Expand Down
Loading

0 comments on commit 21e8faf

Please sign in to comment.