9 changed files with 659 additions and 18 deletions
@ -0,0 +1,71 @@
@@ -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<String, ?>} 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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,47 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
/* |
||||
* 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.RequestParam; |
||||
|
||||
|
||||
/** |
||||
* {@link HttpServiceArgumentResolver} for {@link RequestParam @RequestParam} |
||||
* annotated arguments. |
||||
* |
||||
* <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. |
||||
* |
||||
* <p>The argument may be: |
||||
* <ul> |
||||
* <li>{@code Map<String, ?>} or |
||||
* {@link org.springframework.util.MultiValueMap MultiValueMap<String, ?>} with |
||||
* multiple request parameter and value(s). |
||||
* <li>{@code Collection} or an array of request parameters. |
||||
* <li>An individual request parameter. |
||||
* </ul> |
||||
* |
||||
* <p>Individual request parameters 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 RequestParam#required()} is set to {@code false} |
||||
* <li>{@link RequestParam#defaultValue()} provides a fallback value |
||||
* <li>The argument is declared as {@link java.util.Optional} |
||||
* </ul> |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 6.0 |
||||
*/ |
||||
public class RequestParamArgumentResolver extends AbstractNamedValueArgumentResolver { |
||||
|
||||
|
||||
public RequestParamArgumentResolver(ConversionService conversionService) { |
||||
super(conversionService); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { |
||||
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class); |
||||
return (annot == null ? null : |
||||
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(), "request parameter", true)); |
||||
} |
||||
|
||||
@Override |
||||
protected void addRequestValue(String name, String value, HttpRequestValues.Builder requestValues) { |
||||
requestValues.addRequestParameter(name, value); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
/* |
||||
* 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.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.groovy.util.Maps; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.web.bind.annotation.CookieValue; |
||||
import org.springframework.web.service.annotation.GetExchange; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link RequestHeaderArgumentResolver}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
class CookieValueArgumentResolverTests { |
||||
|
||||
private final TestHttpClientAdapter clientAdapter = new TestHttpClientAdapter(); |
||||
|
||||
private final Service service = this.clientAdapter.createService(Service.class); |
||||
|
||||
|
||||
@Test |
||||
void stringCookie() { |
||||
this.service.executeString("test"); |
||||
assertCookie("cookie", "test"); |
||||
} |
||||
|
||||
@Test |
||||
void objectCookie() { |
||||
this.service.execute(Boolean.TRUE); |
||||
assertCookie("cookie", "true"); |
||||
} |
||||
|
||||
@Test |
||||
void listCookie() { |
||||
this.service.executeList(List.of("test1", Boolean.TRUE, "test3")); |
||||
assertCookie("multiValueCookie", "test1", "true", "test3"); |
||||
} |
||||
|
||||
@Test |
||||
void arrayCookie() { |
||||
this.service.executeArray("test1", Boolean.FALSE, "test3"); |
||||
assertCookie("multiValueCookie", "test1", "false", "test3"); |
||||
} |
||||
|
||||
@Test |
||||
void namedCookie() { |
||||
this.service.executeNamed("test"); |
||||
assertCookie("cookieRenamed", "test"); |
||||
} |
||||
|
||||
@SuppressWarnings("ConstantConditions") |
||||
@Test |
||||
void nullCookieRequired() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeString(null)); |
||||
} |
||||
|
||||
@Test |
||||
void nullCookieNotRequired() { |
||||
this.service.executeNotRequired(null); |
||||
assertCookie("cookie"); |
||||
} |
||||
|
||||
@Test |
||||
void nullCookieWithDefaultValue() { |
||||
this.service.executeWithDefaultValue(null); |
||||
assertCookie("cookie", "default"); |
||||
} |
||||
|
||||
@Test |
||||
void optionalStringCookie() { |
||||
this.service.executeOptional(Optional.of("test")); |
||||
assertCookie("cookie", "test"); |
||||
} |
||||
|
||||
@Test |
||||
void optionalObjectCookie() { |
||||
this.service.executeOptional(Optional.of(Boolean.TRUE)); |
||||
assertCookie("cookie", "true"); |
||||
} |
||||
|
||||
@Test |
||||
void optionalEmpty() { |
||||
this.service.executeOptional(Optional.empty()); |
||||
assertCookie("cookie"); |
||||
} |
||||
|
||||
@Test |
||||
void optionalEmpthyWithDefaultValue() { |
||||
this.service.executeOptionalWithDefaultValue(Optional.empty()); |
||||
assertCookie("cookie", "default"); |
||||
} |
||||
|
||||
@Test |
||||
void mapOfCookies() { |
||||
this.service.executeMap(Maps.of("cookie1", "true", "cookie2", "false")); |
||||
assertCookie("cookie1", "true"); |
||||
assertCookie("cookie2", "false"); |
||||
} |
||||
|
||||
@Test |
||||
void mapOfCookiesIsNull() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeMap(null)); |
||||
} |
||||
|
||||
@Test |
||||
void mapOfCookiesHasOptionalValue() { |
||||
this.service.executeMapWithOptionalValue(Map.of("cookie", Optional.of("test"))); |
||||
assertCookie("cookie", "test"); |
||||
} |
||||
|
||||
private void assertCookie(String key, String... values) { |
||||
List<String> actualValues = this.clientAdapter.getRequestValues().getCookies().get(key); |
||||
if (ObjectUtils.isEmpty(values)) { |
||||
assertThat(actualValues).isNull(); |
||||
} |
||||
else { |
||||
assertThat(actualValues).containsOnly(values); |
||||
} |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") |
||||
private interface Service { |
||||
|
||||
@GetExchange |
||||
void executeString(@CookieValue String cookie); |
||||
|
||||
@GetExchange |
||||
void execute(@CookieValue Object cookie); |
||||
|
||||
@GetExchange |
||||
void executeList(@CookieValue List<Object> multiValueCookie); |
||||
|
||||
@GetExchange |
||||
void executeArray(@CookieValue Object... multiValueCookie); |
||||
|
||||
@GetExchange |
||||
void executeNamed(@CookieValue(name = "cookieRenamed") String cookie); |
||||
|
||||
@GetExchange |
||||
void executeNotRequired(@Nullable @CookieValue(required = false) String cookie); |
||||
|
||||
@GetExchange |
||||
void executeWithDefaultValue(@Nullable @CookieValue(defaultValue = "default") String cookie); |
||||
|
||||
@GetExchange |
||||
void executeOptional(@CookieValue Optional<Object> cookie); |
||||
|
||||
@GetExchange |
||||
void executeOptionalWithDefaultValue(@CookieValue(defaultValue = "default") Optional<Object> cookie); |
||||
|
||||
@GetExchange |
||||
void executeMap(@Nullable @CookieValue Map<String, String> cookie); |
||||
|
||||
@GetExchange |
||||
void executeMapWithOptionalValue(@CookieValue Map<String, Optional<String>> cookies); |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
/* |
||||
* 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.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.web.service.annotation.GetExchange; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link HttpUrlArgumentResolver}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class HttpUrlArgumentResolverTests { |
||||
|
||||
private final TestHttpClientAdapter clientAdapter = new TestHttpClientAdapter(); |
||||
|
||||
private final Service service = this.clientAdapter.createService(Service.class); |
||||
|
||||
|
||||
@Test |
||||
void url() { |
||||
URI dynamicUrl = URI.create("dynamic-path"); |
||||
this.service.execute(dynamicUrl); |
||||
|
||||
assertThat(getRequestValues().getUri()).isEqualTo(dynamicUrl); |
||||
assertThat(getRequestValues().getUriTemplate()).isNull(); |
||||
} |
||||
|
||||
private HttpRequestValues getRequestValues() { |
||||
return this.clientAdapter.getRequestValues(); |
||||
} |
||||
|
||||
|
||||
private interface Service { |
||||
|
||||
@GetExchange("/path") |
||||
void execute(URI uri); |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,104 @@
@@ -0,0 +1,104 @@
|
||||
/* |
||||
* 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.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.web.bind.annotation.RequestParam; |
||||
import org.springframework.web.service.annotation.GetExchange; |
||||
import org.springframework.web.service.annotation.PostExchange; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link RequestParamArgumentResolver}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class RequestParamArgumentResolverTests { |
||||
|
||||
private final TestHttpClientAdapter clientAdapter = new TestHttpClientAdapter(); |
||||
|
||||
private final Service service = this.clientAdapter.createService(Service.class); |
||||
|
||||
|
||||
@Test |
||||
void formData() { |
||||
this.service.postForm("value 1", "value 2"); |
||||
|
||||
Object body = this.clientAdapter.getRequestValues().getBodyValue(); |
||||
assertThat(body).isNotNull().isInstanceOf(byte[].class); |
||||
assertThat(new String((byte[]) body, UTF_8)).isEqualTo("param1=value+1¶m2=value+2"); |
||||
} |
||||
|
||||
@Test |
||||
void uriTemplate() { |
||||
this.service.search("1st value", Arrays.asList("2nd value A", "2nd value B")); |
||||
|
||||
HttpRequestValues requestValues = this.clientAdapter.getRequestValues(); |
||||
|
||||
assertThat(requestValues.getUriTemplate()) |
||||
.isEqualTo("/path?" + |
||||
"{queryParam0}={queryParam0[0]}&" + |
||||
"{queryParam1}={queryParam1[0]}&" + |
||||
"{queryParam1}={queryParam1[1]}"); |
||||
|
||||
assertThat(requestValues.getUriVariables()) |
||||
.containsOnlyKeys("queryParam0", "queryParam1", "queryParam0[0]", "queryParam1[0]", "queryParam1[1]") |
||||
.containsEntry("queryParam0", "param1") |
||||
.containsEntry("queryParam1", "param2") |
||||
.containsEntry("queryParam0[0]", "1st value") |
||||
.containsEntry("queryParam1[0]", "2nd value A") |
||||
.containsEntry("queryParam1[1]", "2nd value B"); |
||||
|
||||
URI uri = UriComponentsBuilder.fromUriString(requestValues.getUriTemplate()) |
||||
.encode().build(requestValues.getUriVariables()); |
||||
|
||||
assertThat(uri.toString()) |
||||
.isEqualTo("/path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B"); |
||||
} |
||||
|
||||
@Test |
||||
void uri() { |
||||
URI baseUrl = URI.create("http://localhost:8080/path"); |
||||
this.service.searchWithDynamicUri(baseUrl, "1st value", Arrays.asList("2nd value A", "2nd value B")); |
||||
|
||||
assertThat(this.clientAdapter.getRequestValues().getUri().toString()) |
||||
.isEqualTo(baseUrl + "?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B"); |
||||
} |
||||
|
||||
|
||||
private interface Service { |
||||
|
||||
@PostExchange(contentType = "application/x-www-form-urlencoded") |
||||
void postForm(@RequestParam String param1, @RequestParam String param2); |
||||
|
||||
@GetExchange("/path") |
||||
void search(@RequestParam String param1, @RequestParam List<String> param2); |
||||
|
||||
@GetExchange |
||||
void searchWithDynamicUri(URI uri, @RequestParam String param1, @RequestParam List<String> param2); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue