Inspect FunctionType to extract collection item type
Resurrects some code from #222 (the test didn't go far enough when that issue was closed).
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.0.BUILD-SNAPSHOT</version>
|
<version>2.1.0.RC1</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
<relativePath /> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.0.M4</version>
|
<version>2.1.0.RC1</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
<relativePath /> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.0.M1</version>
|
<version>2.1.0.RC1</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<spring-cloud-function.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
|
<spring-cloud-function.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
|
||||||
<spring-cloud-stream.version>Fishtown.BUILD-SNAPSHOT</spring-cloud-stream.version>
|
<spring-cloud-stream.version>Fishtown.BUILD-SNAPSHOT</spring-cloud-stream.version>
|
||||||
<reactor.version>3.1.2.RELEASE</reactor.version>
|
<reactor.version>3.1.2.RELEASE</reactor.version>
|
||||||
<wrapper.version>1.0.10.RELEASE</wrapper.version>
|
<wrapper.version>1.0.17.RELEASE</wrapper.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.0.M1</version>
|
<version>2.1.0.RC1</version>
|
||||||
<relativePath />
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.example;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -41,40 +42,38 @@ public class SampleApplicationTests {
|
|||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
private int port;
|
private int port;
|
||||||
|
private TestRestTemplate rest = new TestRestTemplate();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void words() {
|
public void words() {
|
||||||
assertThat(new TestRestTemplate()
|
assertThat(rest.getForObject("http://localhost:" + port + "/words", String.class))
|
||||||
.getForObject("http://localhost:" + port + "/words", String.class))
|
|
||||||
.isEqualTo("[{\"value\":\"foo\"},{\"value\":\"bar\"}]");
|
.isEqualTo("[{\"value\":\"foo\"},{\"value\":\"bar\"}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void uppercase() {
|
public void uppercase() {
|
||||||
assertThat(new TestRestTemplate().postForObject(
|
assertThat(rest.postForObject("http://localhost:" + port + "/uppercase",
|
||||||
"http://localhost:" + port + "/uppercase", "[{\"value\":\"foo\"}]",
|
"[{\"value\":\"foo\"}]", String.class))
|
||||||
String.class)).isEqualTo("[{\"value\":\"FOO\"}]");
|
.isEqualTo("[{\"value\":\"FOO\"}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void composite() {
|
public void composite() {
|
||||||
assertThat(new TestRestTemplate()
|
assertThat(rest.getForObject("http://localhost:" + port + "/words,uppercase",
|
||||||
.getForObject("http://localhost:" + port + "/words,uppercase", String.class))
|
String.class)).isEqualTo("[{\"value\":\"FOO\"},{\"value\":\"BAR\"}]");
|
||||||
.isEqualTo("[{\"value\":\"FOO\"},{\"value\":\"BAR\"}]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void single() {
|
public void single() {
|
||||||
assertThat(new TestRestTemplate().postForObject(
|
assertThat(rest.postForObject("http://localhost:" + port + "/uppercase",
|
||||||
"http://localhost:" + port + "/uppercase", "{\"value\":\"foo\"}",
|
"{\"value\":\"foo\"}", String.class)).isEqualTo("{\"value\":\"FOO\"}");
|
||||||
String.class)).isEqualTo("{\"value\":\"FOO\"}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lowercase() {
|
public void lowercase() {
|
||||||
assertThat(new TestRestTemplate().postForObject(
|
assertThat(rest.postForObject("http://localhost:" + port + "/lowercase",
|
||||||
"http://localhost:" + port + "/lowercase", "[{\"value\":\"Foo\"}]",
|
"[{\"value\":\"Foo\"}]", String.class))
|
||||||
String.class)).isEqualTo("[{\"value\":\"foo\"}]");
|
.isEqualTo("[{\"value\":\"foo\"}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -85,10 +84,25 @@ public class SampleApplicationTests {
|
|||||||
map.put("A", Arrays.asList("1", "2", "3"));
|
map.put("A", Arrays.asList("1", "2", "3"));
|
||||||
map.put("B", Arrays.asList("5", "6"));
|
map.put("B", Arrays.asList("5", "6"));
|
||||||
|
|
||||||
assertThat(new TestRestTemplate().exchange(RequestEntity.post(new URI("http://localhost:" + port + "/sum"))
|
assertThat(rest.exchange(
|
||||||
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
RequestEntity.post(new URI("http://localhost:" + port + "/sum"))
|
||||||
.body(map), String.class).getBody())
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
.isEqualTo("[{\"A\":6,\"B\":11}]");
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED).body(map),
|
||||||
|
String.class).getBody()).isEqualTo("[{\"A\":6,\"B\":11}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void multipart() throws Exception {
|
||||||
|
|
||||||
|
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
|
map.put("A", Arrays.asList("1", "2", "3"));
|
||||||
|
map.put("B", Arrays.asList("5", "6"));
|
||||||
|
|
||||||
|
assertThat(rest.exchange(
|
||||||
|
RequestEntity.post(new URI("http://localhost:" + port + "/sum")).accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.MULTIPART_FORM_DATA).body(map),
|
||||||
|
String.class).getBody()).isEqualTo("[{\"A\":6,\"B\":11}]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.0.M1</version>
|
<version>2.1.0.RC1</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<spring-cloud-function.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
|
<spring-cloud-function.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
|
||||||
<wrapper.version>1.0.13.RELEASE</wrapper.version>
|
<wrapper.version>1.0.17.RELEASE</wrapper.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -60,6 +60,11 @@
|
|||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.synchronoss.cloud</groupId>
|
||||||
|
<artifactId>nio-multipart-parser</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.cloud.function.web;
|
package org.springframework.cloud.function.web;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -97,15 +99,14 @@ public class RequestProcessor {
|
|||||||
boolean stream) {
|
boolean stream) {
|
||||||
Object function = wrapper.handler();
|
Object function = wrapper.handler();
|
||||||
Class<?> inputType = inspector.getInputType(function);
|
Class<?> inputType = inspector.getInputType(function);
|
||||||
|
Type itemType = getItemType(function);
|
||||||
|
|
||||||
Object input = null;
|
Object input = null;
|
||||||
if (StringUtils.hasText(body)) {
|
if (StringUtils.hasText(body)) {
|
||||||
if (body.startsWith("[")) {
|
if (body.startsWith("[")) {
|
||||||
input = Collection.class.isAssignableFrom(inputType)
|
input = mapper.toObject(body, ResolvableType
|
||||||
? mapper.toObject(body, inputType)
|
.forClassWithGenerics(ArrayList.class, (Class<?>) itemType)
|
||||||
: mapper.toObject(body,
|
.getType());
|
||||||
ResolvableType.forClassWithGenerics(ArrayList.class,
|
|
||||||
(Class<?>) inputType).getType());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (inputType == String.class) {
|
if (inputType == String.class) {
|
||||||
@@ -146,8 +147,8 @@ public class RequestProcessor {
|
|||||||
form.putAll(params);
|
form.putAll(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean inputIsCollection =
|
boolean inputIsCollection = Collection.class
|
||||||
Collection.class.isAssignableFrom(inspector.getInputType(wrapper.handler()));
|
.isAssignableFrom(inspector.getInputType(wrapper.handler()));
|
||||||
Flux<?> flux = body == null ? Flux.just(form)
|
Flux<?> flux = body == null ? Flux.just(form)
|
||||||
: inputIsCollection ? Flux.just(body) : Flux.fromIterable(iterable);
|
: inputIsCollection ? Flux.just(body) : Flux.fromIterable(iterable);
|
||||||
if (inspector.isMessage(function)) {
|
if (inspector.isMessage(function)) {
|
||||||
@@ -254,6 +255,42 @@ public class RequestProcessor {
|
|||||||
return Mono.from(function.apply(input));
|
return Mono.from(function.apply(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getTargetFunction(Object function) {
|
||||||
|
// we need to get the actual un-fluxed function so we can interrogate for types
|
||||||
|
Object target = inspector.getRegistration(function).getTarget();
|
||||||
|
if (target instanceof FluxWrapper) {
|
||||||
|
target = ((FluxWrapper<?>) target).getTarget();
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type getItemType(Object function) {
|
||||||
|
Class<?> inputType = inspector.getInputType(function);
|
||||||
|
if (!Collection.class.isAssignableFrom(inputType)) {
|
||||||
|
return inputType;
|
||||||
|
}
|
||||||
|
Type type = inspector.getRegistration(this.getTargetFunction(function)).getType()
|
||||||
|
.getType();
|
||||||
|
if (type instanceof ParameterizedType) {
|
||||||
|
type = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (Type iface : ((Class<?>) type).getGenericInterfaces()) {
|
||||||
|
if (iface.getTypeName().startsWith("java.util.function")) {
|
||||||
|
type = ((ParameterizedType) iface).getActualTypeArguments()[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type instanceof ParameterizedType) {
|
||||||
|
type = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = inputType;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
public static class FunctionWrapper {
|
public static class FunctionWrapper {
|
||||||
|
|
||||||
private final Function<Publisher<?>, Publisher<?>> function;
|
private final Function<Publisher<?>, Publisher<?>> function;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.function.test;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -45,15 +46,43 @@ public class FunctionalWithInputCollectionTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void words() throws Exception {
|
public void words() throws Exception {
|
||||||
client.post().uri("/").body(Mono.just("[\"foo\", \"bar\"]"), String.class).exchange()
|
client.post().uri("/").body(Mono.just("[{\"value\":\"foo\"}, {\"value\":\"bar\"}]"), String.class)
|
||||||
.expectStatus().isOk().expectBody(String.class).isEqualTo("[FOO, BAR]");
|
.exchange().expectStatus().isOk().expectBody(String.class)
|
||||||
|
.isEqualTo("{\"value\":\"FOOBAR\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SpringBootConfiguration
|
@SpringBootConfiguration
|
||||||
protected static class TestConfiguration implements Function<List<String>, String> {
|
protected static class TestConfiguration implements Function<List<Foo>, Foo> {
|
||||||
@Override
|
@Override
|
||||||
public String apply(List<String> value) {
|
public Foo apply(List<Foo> value) {
|
||||||
return value.toString().toUpperCase();
|
return new Foo(value.stream().map(foo -> foo.getValue().toUpperCase())
|
||||||
|
.collect(Collectors.joining()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Foo {
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Foo [value=" + this.value + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user