Skip to content

Commit

Permalink
Add resolvers for URI, cookies, and request params
Browse files Browse the repository at this point in the history
  • Loading branch information
rstoyanchev committed May 3, 2022
1 parent f8ac598 commit 2794553
Show file tree
Hide file tree
Showing 9 changed files with 659 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.web.bind.annotation.CookieValue;


/**
* {@link HttpServiceArgumentResolver} for {@link CookieValue @CookieValue}
* annotated arguments.
*
* <p>The argument may be:
* <ul>
* <li>{@code Map<String, ?>} or
* {@link org.springframework.util.MultiValueMap MultiValueMap&lt;String, ?&gt;} with
* multiple cookies and value(s).
* <li>{@code Collection} or an array of cookie values.
* <li>An individual cookie value.
* </ul>
*
* <p>Individual cookie values may be Strings or Objects to be converted to
* String values through the configured {@link ConversionService}.
*
* <p>If the value is required but {@code null}, {@link IllegalArgumentException}
* is raised. The value is not required if:
* <ul>
* <li>{@link CookieValue#required()} is set to {@code false}
* <li>{@link CookieValue#defaultValue()} provides a fallback value
* <li>The argument is declared as {@link java.util.Optional}
* </ul>
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public class CookieValueArgumentResolver extends AbstractNamedValueArgumentResolver {


public CookieValueArgumentResolver(ConversionService conversionService) {
super(conversionService);
}


@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
CookieValue annot = parameter.getParameterAnnotation(CookieValue.class);
return (annot == null ? null :
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(), "cookie value", true));
}

@Override
protected void addRequestValue(String name, String value, HttpRequestValues.Builder requestValues) {
requestValues.addCookie(name, value);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,29 @@


import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

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.http.codec.FormHttpMessageWriter;
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;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;


/**
Expand Down Expand Up @@ -75,21 +82,20 @@ public final class HttpRequestValues {
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,
private HttpRequestValues(HttpMethod httpMethod,
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
HttpHeaders headers, MultiValueMap<String, String> cookies,
@Nullable Object bodyValue,
@Nullable Publisher<?> body,
@Nullable ParameterizedTypeReference<?> bodyElementType) {
@Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> bodyElementType) {

Assert.isTrue(uri == null || uriTemplate == null, "Expected either URI or URI template, not both");
Assert.isTrue(uri != null || uriTemplate != null, "Neither URI nor URI template");

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.uriTemplate = uriTemplate;
this.uriVariables = uriVariables;
this.headers = headers;
this.cookies = cookies;
this.bodyValue = bodyValue;
this.body = body;
this.bodyElementType = bodyElementType;
Expand Down Expand Up @@ -183,6 +189,8 @@ public static Builder builder(HttpMethod httpMethod) {
*/
public final static class Builder {

private static final Function<MultiValueMap<String, String>, byte[]> FORM_DATA_SERIALIZER = new FormDataSerializer();

private HttpMethod httpMethod;

@Nullable
Expand All @@ -192,14 +200,17 @@ public final static class Builder {
private String uriTemplate;

@Nullable
private Map<String, String> uriVariables;
private Map<String, String> uriVars;

@Nullable
private HttpHeaders headers;

@Nullable
private MultiValueMap<String, String> cookies;

@Nullable
private MultiValueMap<String, String> requestParams;

@Nullable
private Object bodyValue;

Expand Down Expand Up @@ -231,6 +242,7 @@ public Builder setHttpMethod(HttpMethod httpMethod) {
public Builder setUri(URI uri) {
this.uri = uri;
this.uriTemplate = null;
this.uriVars = null;
return this;
}

Expand All @@ -251,8 +263,8 @@ public Builder setUriTemplate(String uriTemplate) {
* {@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.uriVars = (this.uriVars != null ? this.uriVars : new LinkedHashMap<>());
this.uriVars.put(name, value);
this.uri = null;
return this;
}
Expand Down Expand Up @@ -300,6 +312,21 @@ public Builder addCookie(String name, String... values) {
return this;
}

/**
* Add the given request parameter name and values.
* <p>When {@code "content-type"} is set to
* {@code "application/x-www-form-urlencoded"}, request parameters are
* encoded in the request body. Otherwise, they are added as URL query
* parameters.
*/
public Builder addRequestParameter(String name, String... values) {
this.requestParams = (this.requestParams != null ? this.requestParams : new LinkedMultiValueMap<>());
for (String value : values) {
this.requestParams.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
Expand All @@ -326,10 +353,76 @@ public <T, P extends Publisher<T>> void setBody(Publisher<P> body, Parameterized
* Builder the {@link HttpRequestValues} instance.
*/
public HttpRequestValues build() {

URI uri = this.uri;
String uriTemplate = (this.uriTemplate != null || uri != null ? this.uriTemplate : "");
Map<String, String> uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap());

Object bodyValue = this.bodyValue;

if (!CollectionUtils.isEmpty(this.requestParams)) {

boolean isFormData = (this.headers != null &&
MediaType.APPLICATION_FORM_URLENCODED.equals(this.headers.getContentType()));

if (isFormData) {
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request params, not both");
bodyValue = FORM_DATA_SERIALIZER.apply(this.requestParams);
}
else if (uri != null) {
uri = UriComponentsBuilder.fromUri(uri)
.queryParams(UriUtils.encodeQueryParams(this.requestParams))
.build(true)
.toUri();
}
else {
uriVars = (uriVars.isEmpty() ? new HashMap<>() : uriVars);
uriTemplate = appendQueryParams(uriTemplate, uriVars, this.requestParams);
}
}

HttpHeaders headers = HttpHeaders.EMPTY;
if (this.headers != null) {
headers = new HttpHeaders();
headers.putAll(this.headers);
}

MultiValueMap<String, String> cookies = (this.cookies != null ?
new LinkedMultiValueMap<>(this.cookies) : EMPTY_COOKIES_MAP);

return new HttpRequestValues(
this.httpMethod, this.uri, this.uriTemplate, this.uriVariables,
this.headers, this.cookies,
this.bodyValue, this.body, this.bodyElementType);
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, bodyValue,
this.body, this.bodyElementType);
}

private String appendQueryParams(
String uriTemplate, Map<String, String> uriVars, MultiValueMap<String, String> requestParams) {

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uriTemplate);
int i = 0;
for (Map.Entry<String, List<String>> entry : requestParams.entrySet()) {
String nameVar = "queryParam" + i;
uriVars.put(nameVar, entry.getKey());
for (int j = 0; j < entry.getValue().size(); j++) {
String valueVar = nameVar + "[" + j + "]";
uriVars.put(valueVar, entry.getValue().get(j));
uriComponentsBuilder.queryParam("{" + nameVar + "}", "{" + valueVar + "}");
}
i++;
}
return uriComponentsBuilder.build().toUriString();
}

}


private static class FormDataSerializer
extends FormHttpMessageWriter implements Function<MultiValueMap<String, String>, byte[]> {

@Override
public byte[] apply(MultiValueMap<String, String> requestParams) {
Charset charset = StandardCharsets.UTF_8;
return serializeForm(requestParams, charset).getBytes(charset);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionServic
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>(this.customResolvers);
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
resolvers.add(new PathVariableArgumentResolver(conversionService));
resolvers.add(new CookieValueArgumentResolver(conversionService));
resolvers.add(new RequestParamArgumentResolver(conversionService));
resolvers.add(new HttpUrlArgumentResolver());
resolvers.add(new HttpMethodArgumentResolver());
return resolvers;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;


/**
* {@link HttpServiceArgumentResolver} that resolves the target
* request's URL from an {@link HttpMethod} argument.
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public class HttpUrlArgumentResolver implements HttpServiceArgumentResolver {

@Override
public boolean resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {

if (argument instanceof URI uri) {
requestValues.setUri(uri);
return true;
}

return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
*
* <p>The argument may be:
* <ul>
* <li>{@code Map} or {@link org.springframework.util.MultiValueMap} with
* multiple headers and value(s).
* <li>{@code Map<String, ?>} or
* {@link org.springframework.util.MultiValueMap MultiValueMap&lt;String, ?&gt;}
* with multiple headers and value(s).
* <li>{@code Collection} or an array of header values.
* <li>An individual header value.
* </ul>
Expand Down
Loading

0 comments on commit 2794553

Please sign in to comment.