diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/pom.xml index 015e78bfa..8bc840369 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/pom.xml @@ -20,7 +20,7 @@ UTF-8 UTF-8 1.8 - 1.2.1 + 2.0.2 diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandler.java new file mode 100644 index 000000000..720ffd861 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandler.java @@ -0,0 +1,96 @@ +package org.springframework.cloud.function.adapter.aws; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.function.context.catalog.FunctionInspector; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; + +import java.util.HashMap; +import java.util.Map; + +public class SpringBootApiGatewayRequestHandler extends SpringBootRequestHandler { + + @Autowired + private ObjectMapper mapper; + + @Autowired + private FunctionInspector inspector; + + public SpringBootApiGatewayRequestHandler(Class configurationClass) { + super(configurationClass); + } + + public SpringBootApiGatewayRequestHandler() { + super(); + } + + protected Object convertEvent(APIGatewayProxyRequestEvent event) { + Object body = deserializeBody(event.getBody()); + if (functionAcceptsMessage()) { + return new GenericMessage<>(body, getHeaders(event)); + } else { + return body; + } + } + + private boolean functionAcceptsMessage() { + return inspector.isMessage(function()); + } + + private Object deserializeBody(String json) { + try { + return mapper.readValue(json, getInputType()); + } catch (Exception e) { + throw new IllegalStateException("Cannot convert event", e); + } + } + + private MessageHeaders getHeaders(APIGatewayProxyRequestEvent event) { + Map headers = new HashMap<>(); + if (event.getHeaders() != null) { + headers.putAll(event.getHeaders()); + } + headers.put("request", event); + return new MessageHeaders(headers); + } + + protected APIGatewayProxyResponseEvent convertOutput(Object output) { + if (functionReturnsMessage(output)) { + Message message = (Message) output; + return new APIGatewayProxyResponseEvent() + .withStatusCode((Integer) message.getHeaders().getOrDefault("statusCode", 200)) + .withHeaders(toResponseHeaders(message.getHeaders())) + .withBody(serializeBody(message.getPayload())); + } else { + return new APIGatewayProxyResponseEvent() + .withStatusCode(200) + .withBody(serializeBody(output)); + + + } + } + + private boolean functionReturnsMessage(Object output) { + return output instanceof Message; + } + + private Map toResponseHeaders(MessageHeaders messageHeaders) { + Map responseHeaders = new HashMap<>(); + messageHeaders.forEach((key, value) -> responseHeaders.put(key, value.toString())); + return responseHeaders; + } + + private String serializeBody(Object body) { + try { + return mapper.writeValueAsString(body); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Cannot convert output", e); + } + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java index ec1bdec8f..e3385478e 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java @@ -49,7 +49,7 @@ public class SpringBootRequestHandler extends SpringFunctionInitializer im private Object result(Object input, Flux output) { List result = new ArrayList<>(); for (Object value : output.toIterable()) { - result.add(value); + result.add(convertOutput(value)); } if (isSingleValue(input) && result.size()==1) { return result.get(0); @@ -72,4 +72,8 @@ public class SpringBootRequestHandler extends SpringFunctionInitializer im return event; } + protected O convertOutput(Object output) { + return (O) output; + } + } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java index 70ddc65be..7d03799de 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java @@ -136,7 +136,7 @@ public class SpringFunctionInitializer implements Closeable { return Object.class; } - private Object function() { + protected Object function() { return this.function != null ? this.function : (this.consumer != null ? this.consumer : this.supplier); } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandlerTests.java new file mode 100644 index 000000000..d89b9a79e --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandlerTests.java @@ -0,0 +1,108 @@ +package org.springframework.cloud.function.adapter.aws; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import org.junit.Test; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.GenericMessage; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SpringBootApiGatewayRequestHandlerTests { + + private SpringBootApiGatewayRequestHandler handler; + + @Test + public void functionBean() { + handler = new SpringBootApiGatewayRequestHandler(FunctionConfig.class); + handler.initialize(); + + APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); + request.setBody("{\"value\":\"foo\"}"); + + Object output = handler.handleRequest(request, null); + assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); + assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()).isEqualTo(200); + assertThat(((APIGatewayProxyResponseEvent) output).getBody()).isEqualTo("{\"value\":\"FOO\"}"); + } + + @Configuration + @Import({ContextFunctionCatalogAutoConfiguration.class, + JacksonAutoConfiguration.class}) + protected static class FunctionConfig { + @Bean + public Function function() { + return foo -> new Bar(foo.getValue().toUpperCase()); + } + } + + @Test + public void functionMessageBean() { + handler = new SpringBootApiGatewayRequestHandler(FunctionMessageConfig.class); + handler.initialize(); + + APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); + request.setBody("{\"value\":\"foo\"}"); + + Object output = handler.handleRequest(request, null); + assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); + assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()).isEqualTo(200); + assertThat(((APIGatewayProxyResponseEvent) output).getHeaders().get("spring")).isEqualTo("cloud"); + assertThat(((APIGatewayProxyResponseEvent) output).getBody()).isEqualTo("{\"value\":\"FOO\"}"); + } + + @Configuration + @Import({ContextFunctionCatalogAutoConfiguration.class, + JacksonAutoConfiguration.class}) + protected static class FunctionMessageConfig { + @Bean + public Function, Message> function() { + return (foo -> { + Map headers = Collections.singletonMap("spring", "cloud"); + return new GenericMessage<>( + new Bar(foo.getPayload().getValue().toUpperCase()), + headers); + }); + } + } + + protected static class Foo { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + protected static class Bar { + private String value; + + public Bar() { + } + + public Bar(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/spring-cloud-function-samples/function-sample-aws/pom.xml b/spring-cloud-function-samples/function-sample-aws/pom.xml index 8d148a709..66ebd9b17 100644 --- a/spring-cloud-function-samples/function-sample-aws/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws/pom.xml @@ -23,7 +23,7 @@ UTF-8 1.8 1.0.9.RELEASE - 1.2.1 + 2.0.2 3.1.2.RELEASE 1.0.0.BUILD-SNAPSHOT 1.0.0.BUILD-SNAPSHOT