diff --git a/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationMvcTests.java b/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationMvcTests.java index 911b0a4e7..7c32a9e2a 100644 --- a/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationMvcTests.java +++ b/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationMvcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2018 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. @@ -15,36 +15,36 @@ */ package com.example; +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; /** * @author Dave Syer */ @RunWith(SpringRunner.class) -@WebMvcTest +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class SampleApplicationMvcTests { @Autowired - private MockMvc mockMvc; + private TestRestTemplate rest; @Test public void words() throws Exception { - MvcResult mvcResult = mockMvc - .perform(get("/words").accept(MediaType.APPLICATION_JSON)).andReturn(); - mockMvc.perform(asyncDispatch(mvcResult)) - .andExpect(content().string("[{\"value\":\"foo\"},{\"value\":\"bar\"}]")); + ResponseEntity result = rest.exchange(RequestEntity.get(new URI("/words")) + .accept(MediaType.APPLICATION_JSON).build(), String.class); + assertThat(result.getBody()).isEqualTo("[{\"value\":\"foo\"},{\"value\":\"bar\"}]"); } } diff --git a/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationTests.java b/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationTests.java index 8d8664c0a..8ca0f49f8 100644 --- a/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationTests.java +++ b/spring-cloud-function-samples/function-sample-pojo/src/test/java/com/example/SampleApplicationTests.java @@ -15,6 +15,7 @@ */ package com.example; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java index dff610b90..c613c4d45 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java @@ -49,17 +49,17 @@ import reactor.core.publisher.Mono; /** * @author Dave Syer - * + * @author Oleg Zhurakousky */ public class RequestProcessor { private static Log logger = LogFactory.getLog(RequestProcessor.class); - private FunctionInspector inspector; + private final FunctionInspector inspector; - private StringConverter converter; + private final StringConverter converter; - private JsonMapper mapper; + private final JsonMapper mapper; @Value("${debug:${DEBUG:false}}") private String debug = "false"; @@ -76,13 +76,159 @@ public class RequestProcessor { return new FunctionWrapper(function, consumer, supplier); } + public Mono> get(FunctionWrapper wrapper) { + if (wrapper.function() != null) { + return response(wrapper.function(), value(wrapper.function(), wrapper.argument()), true, true); + } + else { + return response(wrapper.supplier(), wrapper.supplier().get(), null, true); + } + } + + public Mono> post(FunctionWrapper wrapper, String body, boolean stream) { + Mono> responseEntityMono; + Object function = wrapper.handler(); + + Object input = null; + if (StringUtils.hasText(body)) { + Class inputType = inspector.getInputType(function); + if (body.startsWith("[")) { + input = mapper.toList(body, inputType); + } + else { + if (inputType == String.class) { + input = body; + } + else if (body.startsWith("{")) { + input = mapper.toSingle(body, inputType); + } + else if (body.startsWith("\"")) { + input = body.substring(1, body.length() - 2); + } + else { + input = converter.convert(function, body); + } + } + } + responseEntityMono = post(wrapper, input, null, stream); + return responseEntityMono; + } + + public Mono> stream(FunctionWrapper request) { + Publisher result = request.function() != null + ? value(request.function(), request.argument()) + : request.supplier().get(); + return stream(request, result); + } + + private Mono> post(FunctionWrapper wrapper, Object body, + MultiValueMap params, boolean stream) { + + Iterable iterable = body instanceof Collection ? (List) body : Collections.singletonList(body); + + Function, Publisher> function = wrapper.function(); + Consumer> consumer = wrapper.consumer(); + + MultiValueMap form = wrapper.params(); + if (params != null) { + form.putAll(params); + } + + Flux flux = body == null ? Flux.just(form) : Flux.fromIterable(iterable); + if (inspector.isMessage(function)) { + flux = messages(wrapper, function == null ? consumer : function, flux); + } + Mono> responseEntityMono = null; + if (function != null) { + Flux result = Flux.from(function.apply(flux)); + logger.debug("Handled POST with function"); + if (stream) { + responseEntityMono = stream(wrapper, result); + } + else { + responseEntityMono = response(function, result, body == null ? null : !(body instanceof Collection), false); + } + } + else if (consumer != null) { + consumer.accept(flux); + logger.debug("Handled POST with consumer"); + responseEntityMono = Mono.just(ResponseEntity.status(HttpStatus.ACCEPTED).build()); + } + return responseEntityMono; + } + + private Flux messages(FunctionWrapper request, Object function, Flux flux) { + Map headers = HeaderUtils.fromHttp(request.headers()); + return flux.map(payload -> MessageUtils.create(function, payload, headers)); + } + + private void addHeaders(BodyBuilder builder, Message message) { + HttpHeaders headers = new HttpHeaders(); + builder.headers(HeaderUtils.fromMessage(message.getHeaders(), headers)); + } + + private Mono> stream(FunctionWrapper request, Publisher result) { + BodyBuilder builder = ResponseEntity.ok(); + if (inspector.isMessage(request.handler())) { + result = Flux.from(result) + .doOnNext(value -> addHeaders(builder, (Message) value)) + .map(message -> MessageUtils.unpack(request.handler(), message) + .getPayload()); + } + + Publisher output = result; + return Flux.from(output).then(Mono.fromSupplier(() -> builder.body(output))); + } + + private Mono> response(Object handler, Publisher result, + Boolean single, boolean getter) { + + BodyBuilder builder = ResponseEntity.ok(); + if (inspector.isMessage(handler)) { + result = Flux.from(result) + .doOnNext(value -> addHeaders(builder, (Message) value)) + .map(message -> MessageUtils.unpack(handler, message).getPayload()); + } + + if (isOutputSingle(handler) && (single != null && single || getter || isInputMultiple(handler))) { + result = Mono.from(result); + } + + if (result instanceof Flux) { + result = Flux.from(result).collectList(); + } + return Mono.from(result).flatMap(body -> Mono.just(builder.body(body))); + } + + private boolean isInputMultiple(Object handler) { + Class type = inspector.getInputType(handler); + Class wrapper = inspector.getInputWrapper(handler); + return Collection.class.isAssignableFrom(type) || Flux.class.equals(wrapper); + } + + private boolean isOutputSingle(Object handler) { + Class type = inspector.getOutputType(handler); + Class wrapper = inspector.getOutputWrapper(handler); + if (Stream.class.isAssignableFrom(type)) { + return false; + } + else { + return wrapper == type || Mono.class.equals(wrapper) || Optional.class.equals(wrapper); + } + } + + private Mono value(Function, Publisher> function, String value) { + Object input = converter.convert(function, value); + return Mono.from(function.apply(Flux.just(input))); + } + public static class FunctionWrapper { - private Function, Publisher> function; + private final Function, Publisher> function; - private Consumer> consumer; + private final Consumer> consumer; - private Supplier> supplier; + private final Supplier> supplier; private MultiValueMap params = new LinkedMultiValueMap<>(); @@ -140,178 +286,4 @@ public class RequestProcessor { return this.argument; } } - - public Mono> post(FunctionWrapper wrapper, String body, - boolean stream) { - - Object function = wrapper.handler(); - if (!StringUtils.hasText(body)) { - return post(wrapper, (List) null, null, stream); - } - body = body.trim(); - Object input; - Class inputType = inspector.getInputType(function); - if (body.startsWith("[")) { - input = mapper.toList(body, inputType); - } - else { - if (inputType == String.class) { - input = body; - } - else if (body.startsWith("{")) { - input = mapper.toSingle(body, inputType); - } - else if (body.startsWith("\"")) { - input = body.substring(1, body.length() - 2); - } - else { - input = converter.convert(function, body); - } - } - if (input instanceof List) { - return post(wrapper, (List) input, null, stream); - } - return post(wrapper, Collections.singletonList(input), null, stream); - } - - private Mono> post(FunctionWrapper wrapper, List body, - MultiValueMap params, boolean stream) { - - Function, Publisher> function = wrapper.function(); - Consumer> consumer = wrapper.consumer(); - - MultiValueMap form = wrapper.params(); - if (params != null) { - form.putAll(params); - } - - Flux flux = body == null ? Flux.just(form) : Flux.fromIterable(body); - if (inspector.isMessage(function)) { - flux = messages(wrapper, function == null ? consumer : function, flux); - } - if (function != null) { - Flux result = Flux.from(function.apply(flux)); - if (logger.isDebugEnabled()) { - logger.debug("Handled POST with function"); - } - if (stream) { - return stream(wrapper, result); - } - return response(function, result, body == null ? null : body.size()<=1, false); - } - - if (consumer != null) { - consumer.accept(flux); - if (logger.isDebugEnabled()) { - logger.debug("Handled POST with consumer"); - } - return Mono.just(ResponseEntity.status(HttpStatus.ACCEPTED).build()); - } - - throw new IllegalArgumentException("no such function"); - } - - private Flux messages(FunctionWrapper request, Object function, Flux flux) { - Map headers = HeaderUtils.fromHttp(request.headers()); - flux = flux.map(payload -> MessageUtils.create(function, payload, headers)); - return flux; - } - - private void addHeaders(BodyBuilder builder, Message message) { - HttpHeaders headers = new HttpHeaders(); - builder.headers(HeaderUtils.fromMessage(message.getHeaders(), headers)); - } - - public Mono> stream(FunctionWrapper request) { - Publisher result; - if (request.function()!=null) { - result = value(request.function(), request.argument()); - } else { - result = supplier(request.supplier()); - } - return stream(request, result); - } - - - private Mono> stream(FunctionWrapper request, Publisher result) { - - BodyBuilder builder = ResponseEntity.ok(); - if (inspector.isMessage(request.handler())) { - result = Flux.from(result) - .doOnNext(value -> addHeaders(builder, (Message) value)) - .map(message -> MessageUtils.unpack(request.handler(), message) - .getPayload()); - } - - Publisher output = result; - return Flux.from(output).then(Mono.fromSupplier(() -> builder.body(output))); - - } - - private Mono> response(Object handler, Publisher result, - Boolean single, boolean getter) { - - BodyBuilder builder = ResponseEntity.ok(); - if (inspector.isMessage(handler)) { - result = Flux.from(result) - .doOnNext(value -> addHeaders(builder, (Message) value)) - .map(message -> MessageUtils.unpack(handler, message).getPayload()); - } - - if (single != null && single && isOutputSingle(handler)) { - result = Mono.from(result); - } - else if (getter && single == null && isOutputSingle(handler)) { - result = Mono.from(result); - } - else if (isInputMultiple(handler) && isOutputSingle(handler)) { - result = Mono.from(result); - } - Publisher output = result; - if (output instanceof Mono) { - return Mono.from(output).flatMap(body -> Mono.just(builder.body(body))); - } - return Flux.from(output).collectList() - .flatMap(body -> Mono.just(builder.body(body))); - } - - private boolean isInputMultiple(Object handler) { - Class type = inspector.getInputType(handler); - Class wrapper = inspector.getInputWrapper(handler); - return Collection.class.isAssignableFrom(type) || Flux.class.equals(wrapper); - } - - private boolean isOutputSingle(Object handler) { - Class type = inspector.getOutputType(handler); - Class wrapper = inspector.getOutputWrapper(handler); - if (Stream.class.isAssignableFrom(type)) { - return false; - } - if (wrapper == type) { - return true; - } - return Mono.class.equals(wrapper) || Optional.class.equals(wrapper); - } - - private Publisher supplier(Supplier> supplier) { - Publisher result = supplier.get(); - return result; - } - - private Mono value(Function, Publisher> function, String value) { - Object input = converter.convert(function, value); - Mono result = Mono.from(function.apply(Flux.just(input))); - return result; - } - - public Mono> get(FunctionWrapper wrapper) { - if (wrapper.function() != null) { - return response(wrapper.function(), value(wrapper.function(), wrapper.argument()), true, true); - } - else { - return response(wrapper.supplier(), supplier(wrapper.supplier()), null, true); - } - } - - } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java index 94033a428..e251682ec 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2017-2018 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. @@ -65,7 +65,6 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - // this.controller.setDebug(!"false".equals(debug)); detectHandlerMethods(controller); while (prefix.endsWith("/")) { prefix = prefix.substring(0, prefix.length() - 1); @@ -126,30 +125,39 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping return null; } path = path.startsWith("/") ? path.substring(1) : path; + + Object functionForGet = null; Supplier> supplier = functions.lookup(Supplier.class, path); if (supplier != null) { request.getAttributes().put(WebRequestConstants.SUPPLIER, supplier); - return supplier; + functionForGet = supplier; } - StringBuilder builder = new StringBuilder(); - String name = path; - String value = null; - for (String element : path.split("/")) { - if (builder.length() > 0) { - builder.append("/"); - } - builder.append(element); - name = builder.toString(); - value = path.length() > name.length() ? path.substring(name.length() + 1) - : null; - Function function = functions.lookup(Function.class, name); - if (function != null) { - request.getAttributes().put(WebRequestConstants.FUNCTION, function); - request.getAttributes().put(WebRequestConstants.ARGUMENT, value); - return function; + else { + StringBuilder builder = new StringBuilder(); + String name = path; + String[] splitPath = path.split("/"); + Function function = null; + for (int i = 0; i < splitPath.length || function != null; i++) { + String element = splitPath[i]; + if (builder.length() > 0) { + builder.append("/"); + } + builder.append(element); + name = builder.toString(); + + function = functions.lookup(Function.class, name); + if (function != null) { + request.getAttributes().put(WebRequestConstants.FUNCTION, function); + String value = path.length() > name.length() + ? path.substring(name.length() + 1) + : null; + request.getAttributes().put(WebRequestConstants.ARGUMENT, value); + functionForGet = function; + } } } - return null; + + return functionForGet; } }