Refactor HttpRequestSpec to HttpRequestValues

HttpRequestValues is immutable and exposes a builder.

See gh-28386
This commit is contained in:
rstoyanchev
2022-04-27 09:25:23 +01:00
parent 564f8ba7a0
commit d7ab5b4132
12 changed files with 484 additions and 348 deletions

View File

@@ -33,18 +33,18 @@ import org.springframework.http.ResponseEntity;
*/
public interface HttpClientAdapter {
Mono<Void> requestToVoid(HttpRequestSpec spec);
Mono<Void> requestToVoid(HttpRequestValues requestValues);
Mono<HttpHeaders> requestToHeaders(HttpRequestSpec spec);
Mono<HttpHeaders> requestToHeaders(HttpRequestValues requestValues);
<T> Mono<T> requestToBody(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType);
<T> Mono<T> requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
<T> Flux<T> requestToBodyFlux(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType);
<T> Flux<T> requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestSpec spec);
Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestValues requestValues);
<T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType);
<T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
<T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType);
<T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
}

View File

@@ -38,7 +38,7 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
@Override
public void resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec) {
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (argument == null) {
return;
@@ -47,7 +47,7 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
if (logger.isTraceEnabled()) {
logger.trace("Resolved HTTP method to: " + httpMethod.name());
}
requestSpec.setHttpMethod(httpMethod);
requestValues.setHttpMethod(httpMethod);
}
}

View File

@@ -1,196 +0,0 @@
/*
* 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.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Container for HTTP request values extracted from an
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
* method and argument values passed to it. This is then given to
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public class HttpRequestSpec {
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
CollectionUtils.toMultiValueMap(Collections.emptyMap());
@Nullable
private URI uri;
@Nullable
private String uriTemplate;
@Nullable
private Map<String, String> uriVariables;
@Nullable
private List<String> uriVariablesList;
@Nullable
private HttpMethod httpMethod;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private Object bodyValue;
@Nullable
private Publisher<?> bodyPublisher;
@Nullable
private ParameterizedTypeReference<?> bodyPublisherElementType;
private boolean complete;
public HttpRequestSpec() {
}
public void setUri(URI uri) {
checkComplete();
this.uri = uri;
}
@Nullable
public URI getUri() {
return this.uri;
}
public void setUriTemplate(String uriTemplate) {
checkComplete();
this.uriTemplate = uriTemplate;
}
@Nullable
public String getUriTemplate() {
return this.uriTemplate;
}
public Map<String, String> getUriVariables() {
this.uriVariables = (this.uriVariables != null ? this.uriVariables : new LinkedHashMap<>());
return this.uriVariables;
}
public List<String> getUriVariableValues() {
this.uriVariablesList = (this.uriVariablesList != null ? this.uriVariablesList : new ArrayList<>());
return this.uriVariablesList;
}
public void setHttpMethod(HttpMethod httpMethod) {
checkComplete();
this.httpMethod = httpMethod;
}
@Nullable
public HttpMethod getHttpMethod() {
return this.httpMethod;
}
public HttpMethod getHttpMethodRequired() {
Assert.notNull(this.httpMethod, "No HttpMethod");
return this.httpMethod;
}
public HttpHeaders getHeaders() {
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
return this.headers;
}
public MultiValueMap<String, String> getCookies() {
this.cookies = (this.cookies != null ? this.cookies : new LinkedMultiValueMap<>());
return this.cookies;
}
public void setBodyValue(Object bodyValue) {
checkComplete();
this.bodyValue = bodyValue;
}
@Nullable
public Object getBodyValue() {
return this.bodyValue;
}
public <T, P extends Publisher<T>> void setBodyPublisher(Publisher<P> bodyPublisher, MethodParameter parameter) {
checkComplete();
// Adapt to Mono/Flux and nest MethodParameter for element type
this.bodyPublisher = bodyPublisher;
this.bodyPublisherElementType = ParameterizedTypeReference.forType(parameter.nested().getGenericParameterType());
}
@Nullable
public Publisher<?> getBodyPublisher() {
return this.bodyPublisher;
}
public ParameterizedTypeReference<?> getBodyPublisherElementType() {
Assert.state(this.bodyPublisherElementType != null, "No body Publisher");
return this.bodyPublisherElementType;
}
private void checkComplete() {
Assert.isTrue(!this.complete, "setComplete already called");
}
void setComplete() {
this.complete = true;
this.uriVariables = (this.uriVariables != null ?
Collections.unmodifiableMap(this.uriVariables) : Collections.emptyMap());
this.uriVariablesList = (this.uriVariablesList != null ?
Collections.unmodifiableList(this.uriVariablesList) : Collections.emptyList());
this.headers = (this.headers != null ?
HttpHeaders.readOnlyHttpHeaders(this.headers) : HttpHeaders.EMPTY);
this.cookies = (this.cookies != null ?
CollectionUtils.unmodifiableMultiValueMap(this.cookies) : EMPTY_COOKIES_MAP);
}
}

View File

@@ -0,0 +1,337 @@
/*
* 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.net.URI;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Container for HTTP request values extracted from an
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
* method and argument values passed to it. This is then given to
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public final class HttpRequestValues {
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
CollectionUtils.toMultiValueMap(Collections.emptyMap());
private final HttpMethod httpMethod;
@Nullable
private final URI uri;
@Nullable
private final String uriTemplate;
private final Map<String, String> uriVariables;
private final HttpHeaders headers;
private final MultiValueMap<String, String> cookies;
@Nullable
private final Object bodyValue;
@Nullable
private final Publisher<?> body;
@Nullable
private final ParameterizedTypeReference<?> bodyElementType;
private HttpRequestValues(HttpMethod httpMethod, @Nullable URI uri,
@Nullable String uriTemplate, @Nullable Map<String, String> uriVariables,
@Nullable HttpHeaders headers, @Nullable MultiValueMap<String, String> cookies,
@Nullable Object bodyValue,
@Nullable Publisher<?> body,
@Nullable ParameterizedTypeReference<?> bodyElementType) {
Assert.isTrue(uri == null || uriTemplate == null, "Expected either URI or URI template, not both");
this.httpMethod = httpMethod;
this.uri = uri;
this.uriTemplate = (uri != null || uriTemplate != null ? uriTemplate : "");
this.uriVariables = (uriVariables != null ? uriVariables : Collections.emptyMap());
this.headers = (headers != null ? headers : HttpHeaders.EMPTY);
this.cookies = (cookies != null ? cookies : EMPTY_COOKIES_MAP);
this.bodyValue = bodyValue;
this.body = body;
this.bodyElementType = bodyElementType;
}
/**
* Return the HTTP method to use for the request.
*/
public HttpMethod getHttpMethod() {
return this.httpMethod;
}
/**
* Return the full URL to use, if set.
* <p>This is mutually exclusive with {@link #getUriTemplate() uriTemplate}.
* One of the two has a value but not both.
*/
@Nullable
public URI getUri() {
return this.uri;
}
/**
* Return the URL template for the request, if set.
* <p>This is mutually exclusive with a {@link #getUri() full URL}.
* One of the two has a value but not both.
*/
@Nullable
public String getUriTemplate() {
return this.uriTemplate;
}
/**
* Return the URL template variables, or an empty map.
*/
public Map<String, String> getUriVariables() {
return this.uriVariables;
}
/**
* Return the headers for the request, if any.
*/
public HttpHeaders getHeaders() {
return this.headers;
}
/**
* Return the cookies for the request, if any.
*/
public MultiValueMap<String, String> getCookies() {
return this.cookies;
}
/**
* Return the request body as a value to be serialized, if set.
* <p>This is mutually exclusive with {@link #getBody()}.
* Only one of the two or neither is set.
*/
@Nullable
public Object getBodyValue() {
return this.bodyValue;
}
/**
* Return the request body as a Publisher.
* <p>This is mutually exclusive with {@link #getBodyValue()}.
* Only one of the two or neither is set.
*/
@Nullable
public Publisher<?> getBody() {
return this.body;
}
/**
* Return the element type for a {@link #getBody() Publisher body}.
*/
@Nullable
public ParameterizedTypeReference<?> getBodyElementType() {
return this.bodyElementType;
}
public static Builder builder(HttpMethod httpMethod) {
return new Builder(httpMethod);
}
/**
* Builder for {@link HttpRequestValues}.
*/
public final static class Builder {
private HttpMethod httpMethod;
@Nullable
private URI uri;
@Nullable
private String uriTemplate;
@Nullable
private Map<String, String> uriVariables;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private Object bodyValue;
@Nullable
private Publisher<?> body;
@Nullable
private ParameterizedTypeReference<?> bodyElementType;
private Builder(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "HttpMethod is required");
this.httpMethod = httpMethod;
}
/**
* Set the HTTP method for the request.
*/
public Builder setHttpMethod(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "HttpMethod is required");
this.httpMethod = httpMethod;
return this;
}
/**
* Set the request URL as a full URL.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUriTemplate(String)}.
*/
public Builder setUri(URI uri) {
this.uri = uri;
this.uriTemplate = null;
return this;
}
/**
* Set the request URL as a String template.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUri(URI) full URI}.
*/
public Builder setUriTemplate(String uriTemplate) {
this.uriTemplate = uriTemplate;
this.uri = null;
return this;
}
/**
* Add a URI variable name-value pair.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUri(URI) full URI}.
*/
public Builder setUriVariable(String name, String value) {
this.uriVariables = (this.uriVariables != null ? this.uriVariables : new LinkedHashMap<>());
this.uriVariables.put(name, value);
this.uri = null;
return this;
}
/**
* Set the media types for the request {@code Accept} header.
*/
public Builder setAccept(List<MediaType> acceptableMediaTypes) {
initHeaders().setAccept(acceptableMediaTypes);
return this;
}
/**
* Set the media type for the request {@code Content-Type} header.
*/
public Builder setContentType(MediaType contentType) {
initHeaders().setContentType(contentType);
return this;
}
/**
* Add the given header name and values.
*/
public Builder addHeader(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
initHeaders().add(headerName, headerValue);
}
return this;
}
private HttpHeaders initHeaders() {
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
return this.headers;
}
/**
* Add the given cookie name and values.
*/
public Builder addCookie(String name, String... values) {
this.cookies = (this.cookies != null ? this.cookies : new LinkedMultiValueMap<>());
for (String value : values) {
this.cookies.add(name, value);
}
return this;
}
/**
* Set the request body as a concrete value to be serialized.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setBody(Publisher, ParameterizedTypeReference) body Publisher}.
*/
public void setBodyValue(Object bodyValue) {
this.bodyValue = bodyValue;
this.body = null;
this.bodyElementType = null;
}
/**
* Set the request body as a concrete value to be serialized.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setBodyValue(Object) body value}.
*/
public <T, P extends Publisher<T>> void setBody(Publisher<P> body, ParameterizedTypeReference<?> elementTye) {
this.body = body;
this.bodyElementType = elementTye;
this.bodyValue = null;
}
/**
* Builder the {@link HttpRequestValues} instance.
*/
public HttpRequestValues build() {
return new HttpRequestValues(
this.httpMethod, this.uri, this.uriTemplate, this.uriVariables,
this.headers, this.cookies,
this.bodyValue, this.body, this.bodyElementType);
}
}
}

View File

@@ -34,8 +34,8 @@ public interface HttpServiceArgumentResolver {
* Resolve the argument value.
* @param argument the argument value
* @param parameter the method parameter for the argument
* @param requestSpec container to add HTTP request values to
* @param requestValues builder to add HTTP request values to
*/
void resolve(@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec);
void resolve(@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues);
}

View File

@@ -62,7 +62,7 @@ final class HttpServiceMethod {
private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestSpecFactory requestSpecFactory;
private final HttpRequestValuesInitializer requestValuesInitializer;
private final ResponseFunction responseFunction;
@@ -75,7 +75,7 @@ final class HttpServiceMethod {
this.method = method;
this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers;
this.requestSpecFactory = HttpRequestSpecFactory.create(method, containingClass);
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass);
this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout);
}
@@ -96,35 +96,34 @@ final class HttpServiceMethod {
@Nullable
public Object invoke(Object[] arguments) {
HttpRequestSpec requestSpec = this.requestSpecFactory.initializeRequestSpec();
applyArguments(requestSpec, arguments);
requestSpec.setComplete();
return this.responseFunction.execute(requestSpec);
HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder();
applyArguments(requestValues, arguments);
return this.responseFunction.execute(requestValues.build());
}
private void applyArguments(HttpRequestSpec requestSpec, Object[] arguments) {
private void applyArguments(HttpRequestValues.Builder requestValues, Object[] arguments) {
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 (HttpServiceArgumentResolver resolver : this.argumentResolvers) {
resolver.resolve(argumentValue, this.parameters[i], requestSpec);
resolver.resolve(argumentValue, this.parameters[i], requestValues);
}
}
}
/**
* Factory for an {@link HttpRequestSpec} with values extracted from
* Factory for an {@link HttpRequestValues} with values extracted from
* the type and method-level {@link HttpExchange @HttpRequest} annotations.
*/
private record HttpRequestSpecFactory(
@Nullable HttpMethod httpMethod, @Nullable String url,
private record HttpRequestValuesInitializer(
HttpMethod httpMethod, @Nullable String url,
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
private HttpRequestSpecFactory(
@Nullable HttpMethod httpMethod, @Nullable String url,
private HttpRequestValuesInitializer(
HttpMethod httpMethod, @Nullable String url,
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
this.url = url;
@@ -133,28 +132,25 @@ final class HttpServiceMethod {
this.acceptMediaTypes = acceptMediaTypes;
}
public HttpRequestSpec initializeRequestSpec() {
HttpRequestSpec requestSpec = new HttpRequestSpec();
if (this.httpMethod != null) {
requestSpec.setHttpMethod(this.httpMethod);
}
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
HttpRequestValues.Builder requestValues = HttpRequestValues.builder(this.httpMethod);
if (this.url != null) {
requestSpec.setUriTemplate(this.url);
requestValues.setUriTemplate(this.url);
}
if (this.contentType != null) {
requestSpec.getHeaders().setContentType(this.contentType);
requestValues.setContentType(this.contentType);
}
if (this.acceptMediaTypes != null) {
requestSpec.getHeaders().setAccept(this.acceptMediaTypes);
requestValues.setAccept(this.acceptMediaTypes);
}
return requestSpec;
return requestValues;
}
/**
* Introspect the method and create the request factory for it.
*/
public static HttpRequestSpecFactory create(Method method, Class<?> containingClass) {
public static HttpRequestValuesInitializer create(Method method, Class<?> containingClass) {
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
@@ -166,11 +162,10 @@ final class HttpServiceMethod {
MediaType contentType = initContentType(annot1, annot2);
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
return new HttpRequestSpecFactory(httpMethod, url, contentType, acceptableMediaTypes);
return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
}
@Nullable
private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
String value1 = (typeAnnot != null ? typeAnnot.method() : null);
@@ -184,7 +179,7 @@ final class HttpServiceMethod {
return HttpMethod.valueOf(value1);
}
return null;
throw new IllegalStateException("HttpMethod is required");
}
@Nullable
@@ -249,12 +244,12 @@ final class HttpServiceMethod {
* return type blocking if necessary.
*/
private record ResponseFunction(
Function<HttpRequestSpec, Publisher<?>> responseFunction,
Function<HttpRequestValues, Publisher<?>> responseFunction,
@Nullable ReactiveAdapter returnTypeAdapter,
boolean blockForOptional, Duration blockTimeout) {
private ResponseFunction(
Function<HttpRequestSpec, Publisher<?>> responseFunction,
Function<HttpRequestValues, Publisher<?>> responseFunction,
@Nullable ReactiveAdapter returnTypeAdapter,
boolean blockForOptional, Duration blockTimeout) {
@@ -265,9 +260,9 @@ final class HttpServiceMethod {
}
@Nullable
public Object execute(HttpRequestSpec requestSpec) {
public Object execute(HttpRequestValues requestValues) {
Publisher<?> responsePublisher = this.responseFunction.apply(requestSpec);
Publisher<?> responsePublisher = this.responseFunction.apply(requestValues);
if (this.returnTypeAdapter != null) {
return this.returnTypeAdapter.fromPublisher(responsePublisher);
@@ -293,7 +288,7 @@ final class HttpServiceMethod {
MethodParameter actualParam = (reactiveAdapter != null ? returnParam.nested() : returnParam.nestedIfOptional());
Class<?> actualType = actualParam.getNestedParameterType();
Function<HttpRequestSpec, Publisher<?>> responseFunction;
Function<HttpRequestValues, Publisher<?>> responseFunction;
if (actualType.equals(void.class) || actualType.equals(Void.class)) {
responseFunction = client::requestToVoid;
}
@@ -323,7 +318,7 @@ final class HttpServiceMethod {
}
@SuppressWarnings("ConstantConditions")
private static Function<HttpRequestSpec, Publisher<?>> initResponseEntityFunction(
private static Function<HttpRequestValues, Publisher<?>> initResponseEntityFunction(
HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) {
if (reactiveAdapter == null) {
@@ -349,7 +344,7 @@ final class HttpServiceMethod {
});
}
private static Function<HttpRequestSpec, Publisher<?>> initBodyFunction(
private static Function<HttpRequestValues, Publisher<?>> initBodyFunction(
HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) {
ParameterizedTypeReference<?> bodyType =

View File

@@ -56,7 +56,7 @@ public class PathVariableArgumentResolver implements HttpServiceArgumentResolver
@SuppressWarnings("unchecked")
@Override
public void resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec) {
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
if (annotation == null) {
@@ -67,38 +67,38 @@ public class PathVariableArgumentResolver implements HttpServiceArgumentResolver
if (argument != null) {
Assert.isInstanceOf(Map.class, argument);
((Map<String, ?>) argument).forEach((key, value) ->
addUriParameter(key, value, annotation.required(), requestSpec));
addUriParameter(key, value, annotation.required(), requestValues));
}
}
else {
String name = StringUtils.hasText(annotation.value()) ? annotation.value() : annotation.name();
name = StringUtils.hasText(name) ? name : parameter.getParameterName();
Assert.notNull(name, "Failed to determine path variable name for parameter: " + parameter);
addUriParameter(name, argument, annotation.required(), requestSpec);
addUriParameter(name, argument, annotation.required(), requestValues);
}
}
private void addUriParameter(
String name, @Nullable Object value, boolean required, HttpRequestSpec requestSpec) {
String name, @Nullable Object value, boolean required, HttpRequestValues.Builder requestValues) {
if (value instanceof Optional) {
value = ((Optional<?>) value).orElse(null);
}
if (!(value instanceof String)) {
value = this.conversionService.convert(value, String.class);
}
if (value == null) {
Assert.isTrue(!required, "Missing required path variable '" + name + "'");
return;
}
if (!(value instanceof String)) {
value = this.conversionService.convert(value, String.class);
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved path variable '" + name + "' to " + value);
}
requestSpec.getUriVariables().put(name, (String) value);
requestValues.setUriVariable(name, (String) value);
}
}