Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable CompletionStage unwrap in MBW #4643

Merged
merged 1 commit into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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