Add HttpMethod and PathVariable argument resolvers

See gh-28386
This commit is contained in:
Olga Maciaszek-Sharma
2022-04-21 16:06:38 +00:00
committed by rstoyanchev
parent c418768f05
commit c2a008fc22
8 changed files with 979 additions and 112 deletions

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
/**
* An implementation of {@link HttpServiceMethodArgumentResolver} that resolves
* request HTTP method based on argument type. Arguments of type
* {@link HttpMethod} will be used to determine the method.
*
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class HttpMethodArgumentResolver implements HttpServiceMethodArgumentResolver {
private static final Log LOG = LogFactory.getLog(HttpMethodArgumentResolver.class);
@Override
public void resolve(@Nullable Object argument, MethodParameter parameter,
HttpRequestDefinition requestDefinition) {
if (argument == null) {
return;
}
if (argument instanceof HttpMethod httpMethod) {
if (LOG.isTraceEnabled()) {
LOG.trace("Resolved HTTP method to: " + httpMethod.name());
}
requestDefinition.setHttpMethod(httpMethod);
}
}
}

View File

@@ -28,7 +28,9 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
@@ -104,6 +106,8 @@ final class HttpServiceMethod {
Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch");
for (int i = 0; i < this.parameters.length; i++) {
Object argumentValue = arguments[i];
ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
this.parameters[i].initParameterNameDiscovery(nameDiscoverer);
for (HttpServiceMethodArgumentResolver resolver : this.argumentResolvers) {
resolver.resolve(argumentValue, this.parameters[i], requestDefinition);
}

View File

@@ -16,7 +16,6 @@
package org.springframework.web.service.invoker;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
/**
* An implementation of {@link HttpServiceMethodArgumentResolver} that resolves
* request path variables based on method arguments annotated
* with {@link PathVariable}. {@code null} values are allowed only
* if {@link PathVariable#required()} is {@code true}.
*
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class PathVariableArgumentResolver implements HttpServiceMethodArgumentResolver {
private static final Log LOG = LogFactory.getLog(PathVariableArgumentResolver.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
@Nullable
private final ConversionService conversionService;
public PathVariableArgumentResolver(@Nullable ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public void resolve(@Nullable Object argument, MethodParameter parameter,
HttpRequestDefinition requestDefinition) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
if (annotation == null) {
return;
}
String resolvedAnnotationName = StringUtils.hasText(annotation.value())
? annotation.value() : annotation.name();
boolean required = annotation.required();
Object resolvedArgument = resolveFromOptional(argument);
if (resolvedArgument instanceof Map<?, ?> valueMap) {
if (StringUtils.hasText(resolvedAnnotationName)) {
Object value = valueMap.get(resolvedAnnotationName);
Object resolvedValue = resolveFromOptional(value);
addUriParameter(requestDefinition, resolvedAnnotationName, resolvedValue, required);
return;
}
valueMap.entrySet()
.forEach(entry -> addUriParameter(requestDefinition, entry, required));
return;
}
String name = StringUtils.hasText(resolvedAnnotationName)
? resolvedAnnotationName : parameter.getParameterName();
addUriParameter(requestDefinition, name, resolvedArgument, required);
}
private void addUriParameter(HttpRequestDefinition requestDefinition, @Nullable String name,
@Nullable Object value, boolean required) {
if (name == null) {
throw new IllegalStateException("Path variable name cannot be null");
}
String stringValue = getStringValue(value, required);
if (LOG.isTraceEnabled()) {
LOG.trace("Path variable " + name + " resolved to " + stringValue);
}
requestDefinition.getUriVariables().put(name, stringValue);
}
@Nullable
private String getStringValue(@Nullable Object value, boolean required) {
validateForNull(value, required);
validateForReactiveWrapper(value);
return value != null
? convertToString(TypeDescriptor.valueOf(value.getClass()), value) : null;
}
private void addUriParameter(HttpRequestDefinition requestDefinition,
Map.Entry<?, ?> entry, boolean required) {
Object resolvedName = resolveFromOptional(entry.getKey());
String stringName = getStringValue(resolvedName, true);
Object resolvedValue = resolveFromOptional(entry.getValue());
addUriParameter(requestDefinition, stringName, resolvedValue, required);
}
private void validateForNull(@Nullable Object argument, boolean required) {
if (argument == null) {
if (required) {
throw new IllegalStateException("Required variable cannot be null");
}
}
}
private void validateForReactiveWrapper(@Nullable Object object) {
if (object != null) {
Class<?> type = object.getClass();
ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
ReactiveAdapter adapter = adapterRegistry.getAdapter(type);
if (adapter != null) {
throw new IllegalStateException(getClass().getSimpleName() +
" does not support reactive type wrapper: " + type);
}
}
}
@Nullable
private Object resolveFromOptional(@Nullable Object argument) {
if (argument instanceof Optional) {
return ((Optional<?>) argument).orElse(null);
}
return argument;
}
@Nullable
private String convertToString(TypeDescriptor typeDescriptor, @Nullable Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return (String) value;
}
if (this.conversionService != null) {
return (String) this.conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR);
}
return String.valueOf(value);
}
}