From 7083c99fea7e469e511c26533599c2b31c76c418 Mon Sep 17 00:00:00 2001 From: dsolomakha Date: Tue, 24 Mar 2020 20:39:37 -0400 Subject: [PATCH 1/2] PubSub background function adapter --- .../adapter/gcloud/AbstractGcfHandler.java | 88 ++++++++++++++ ...cfSpringBootBackgroundFunctionHandler.java | 51 ++++++++ .../GcfSpringBootHttpRequestHandler.java | 114 +++--------------- .../adapter/gcloud/PubSubMessage.java | 49 ++++++++ .../GcfSpringBootBackgroundFunctionTests.java | 113 +++++++++++++++++ .../GcfSpringBootHttpRequestHandlerTests.java | 75 ++++++------ 6 files changed, 360 insertions(+), 130 deletions(-) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java new file mode 100644 index 000000000..467016923 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java @@ -0,0 +1,88 @@ +package org.springframework.cloud.function.adapter.gcloud; + +import com.google.cloud.functions.Context; +import com.google.cloud.functions.HttpResponse; +import org.reactivestreams.Publisher; +import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; +import org.springframework.messaging.Message; +import reactor.core.publisher.Flux; + +import java.util.*; + +public class AbstractGcfHandler extends AbstractSpringFunctionAdapterInitializer { + public AbstractGcfHandler(Class configurationClass) { + super(configurationClass); + init(); + } + + public AbstractGcfHandler() { + super(); + init(); + } + + public void init() { + Thread.currentThread() + .setContextClassLoader(GcfSpringBootHttpRequestHandler.class.getClassLoader()); + initialize(null); + } + + Object toOptionalIfEmpty(String requestBody) { + return requestBody.isEmpty() ? Optional.empty() : requestBody; + } + + protected boolean functionAcceptsMessage() { + return this.getInspector().isMessage(function()); + } + + @SuppressWarnings("unchecked") + protected T result(Object input, Publisher output, HttpResponse resp) { + List result = new ArrayList<>(); + for (Object value : Flux.from(output).toIterable()) { + result.add((T) convertOutputAndHeaders(value, resp)); + } + if (isSingleValue(input) && result.size() == 1) { + return result.get(0); + } + return (T) result; + } + + + private boolean isSingleValue(Object input) { + return !(input instanceof Collection); + } + + Flux extract(Object input) { + if (input instanceof Collection) { + return Flux.fromIterable((Iterable) input); + } + return Flux.just(input); + } + + protected O convertOutputAndHeaders(Object output, HttpResponse resp) { + if (output instanceof Message) { + Message message = (Message) output; + for (Map.Entry entry : message.getHeaders().entrySet()) { + Object values = entry.getValue(); + if (values instanceof List) { + for (Object value : (List) values) { + if (value != null) { + resp.appendHeader(entry.getKey(), value.toString()); + } + } + } + else if (values != null) { + resp.appendHeader(entry.getKey(), values.toString()); + } + } + return (O) message.getPayload(); + } + else { + return (O) output; + } + } + + boolean returnsOutput() { + return !this.getInspector().getOutputType(function()).equals(Void.class); + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java new file mode 100644 index 000000000..cb687bcd4 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java @@ -0,0 +1,51 @@ +package org.springframework.cloud.function.adapter.gcloud; + +import com.google.cloud.functions.BackgroundFunction; +import com.google.cloud.functions.Context; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; +import reactor.core.publisher.Flux; + +import java.util.HashMap; +import java.util.Map; + +public class GcfSpringBootBackgroundFunctionHandler extends AbstractGcfHandler implements BackgroundFunction { + public GcfSpringBootBackgroundFunctionHandler(Class configurationClass) { + super(configurationClass); + } + + public GcfSpringBootBackgroundFunctionHandler() { + super(); + } + + @Override + public void accept(PubSubMessage pubSubMessage, Context context) { + Flux.from(apply(extract(toMessageIfNeeded(pubSubMessage)))).blockLast(); + } + + private Object toMessageIfNeeded(PubSubMessage pubSubMessage) { + if (functionAcceptsMessage()) { + return new GenericMessage<>(toOptionalIfEmpty(pubSubMessage.getData()), getHeaders(pubSubMessage)); + } + return toOptionalIfEmpty(pubSubMessage.getData()); + } + + private Map getHeaders(PubSubMessage pubSubMessage) { + Map headers = new HashMap(); + + if (pubSubMessage.getAttributes() != null) { + headers.putAll(pubSubMessage.getAttributes()); + } + + if (pubSubMessage.getMessageId() != null) { + headers.put("messageId", pubSubMessage.getMessageId()); + } + + if (pubSubMessage.getPublishTime() != null) { + headers.put("publishTime", pubSubMessage.getPublishTime()); + } + + return new MessageHeaders(headers); + } +} + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java index 6e7999f89..69aa7e1df 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandler.java @@ -16,29 +16,21 @@ package org.springframework.cloud.function.adapter.gcloud; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import com.google.cloud.functions.Context; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; -import com.google.gson.Gson; import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; -import org.springframework.messaging.Message; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.GenericMessage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + /** * Implementation of HttpFunction for Google Cloud Function. * @@ -46,36 +38,32 @@ * * @author Dmitry Solomakha */ -public class GcfSpringBootHttpRequestHandler - extends AbstractSpringFunctionAdapterInitializer implements HttpFunction { +public class GcfSpringBootHttpRequestHandler extends AbstractGcfHandler implements HttpFunction { - private final Gson gson = new Gson(); + public GcfSpringBootHttpRequestHandler(Class configurationClass) { + super(configurationClass); + } public GcfSpringBootHttpRequestHandler() { super(); } - public GcfSpringBootHttpRequestHandler(Class configurationClass) { - super(configurationClass); - } + @Autowired + private ObjectMapper mapper; @Override public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { - Thread.currentThread() - .setContextClassLoader(GcfSpringBootHttpRequestHandler.class.getClassLoader()); - initialize(null); - Publisher output = apply(extract(convert(httpRequest))); BufferedWriter writer = httpResponse.getWriter(); Object result = result(httpRequest, output, httpResponse); if (returnsOutput()) { - writer.write(gson.toJson(result)); + writer.write(mapper.writeValueAsString(result)); writer.flush(); } httpResponse.setStatusCode(200); } - protected Object convert(HttpRequest event) throws IOException { + private Object convert(HttpRequest event) throws IOException { BufferedReader br = event.getReader(); StringBuilder sb = new StringBuilder(); @@ -87,19 +75,12 @@ protected Object convert(HttpRequest event) throws IOException { String requestBody = sb.toString(); if (functionAcceptsMessage()) { - return new GenericMessage<>(toOptionalIfEmpty(requestBody), getHeaders(event, requestBody)); + return new GenericMessage<>(toOptionalIfEmpty(requestBody), getHeaders(event)); } - else { - return toOptionalIfEmpty(requestBody); - } - + return toOptionalIfEmpty(requestBody); } - private Object toOptionalIfEmpty(String requestBody) { - return requestBody.isEmpty() ? Optional.empty() : requestBody; - } - - private MessageHeaders getHeaders(HttpRequest event, String requestBody) { + private MessageHeaders getHeaders(HttpRequest event) { Map headers = new HashMap(); if (event.getHeaders() != null) { @@ -116,63 +97,6 @@ private MessageHeaders getHeaders(HttpRequest event, String requestBody) { headers.put("httpMethod", event.getMethod()); } - headers.put("request", requestBody); return new MessageHeaders(headers); } - - protected boolean functionAcceptsMessage() { - return this.getInspector().isMessage(function()); - } - - @SuppressWarnings("unchecked") - protected T result(Object input, Publisher output, HttpResponse resp) { - List result = new ArrayList<>(); - for (Object value : Flux.from(output).toIterable()) { - result.add((T) convertOutputAndHeaders(value, resp)); - } - if (isSingleValue(input) && result.size() == 1) { - return result.get(0); - } - return (T) result; - } - - - private boolean isSingleValue(Object input) { - return !(input instanceof Collection); - } - - private Flux extract(Object input) { - if (input instanceof Collection) { - return Flux.fromIterable((Iterable) input); - } - return Flux.just(input); - } - - protected O convertOutputAndHeaders(Object output, HttpResponse resp) { - if (output instanceof Message) { - Message message = (Message) output; - for (Map.Entry entry : message.getHeaders().entrySet()) { - Object values = entry.getValue(); - if (values instanceof List) { - for (Object value : (List) values) { - if (value != null) { - resp.appendHeader(entry.getKey(), value.toString()); - } - } - } - else if (values != null) { - resp.appendHeader(entry.getKey(), values.toString()); - } - } - return (O) message.getPayload(); - } - else { - return (O) output; - } - } - - private boolean returnsOutput() { - return !this.getInspector().getOutputType(function()).equals(Void.class); - } - } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java new file mode 100644 index 000000000..05ce6a5e4 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java @@ -0,0 +1,49 @@ +package org.springframework.cloud.function.adapter.gcloud; + +import java.util.Map; + +class PubSubMessage { + String data; + Map attributes; + String messageId; + String publishTime; + + public PubSubMessage(String data, Map attributes, String messageId, String publishTime) { + this.data = data; + this.attributes = attributes; + this.messageId = messageId; + this.publishTime = publishTime; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String getPublishTime() { + return publishTime; + } + + public void setPublishTime(String publishTime) { + this.publishTime = publishTime; + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java new file mode 100644 index 000000000..016b2bfde --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019-2019 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.cloud.function.adapter.gcloud; + +import com.google.gson.Gson; +import org.junit.After; +import org.junit.Test; +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.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandlerTests.*; + +/** + * @author Dmitry Solomakha + */ +public class GcfSpringBootBackgroundFunctionTests { + private GcfSpringBootBackgroundFunctionHandler handler = null; + public static final Gson GSON = new Gson(); + + GcfSpringBootBackgroundFunctionHandler handler(Class config) { + GcfSpringBootBackgroundFunctionHandler handler = new GcfSpringBootBackgroundFunctionHandler<>(config); + + this.handler = handler; + return handler; + } + + @Test + public void testWithBody() { + GcfSpringBootBackgroundFunctionHandler handler = handler(FunctionConfig.class); + + Foo foo = new Foo("foo"); + PubSubMessage psMessage = new PubSubMessage(GSON.toJson(foo), null, null, null); + handler.accept(psMessage, null); + + assertThat(FunctionConfig.argument).isEqualTo(foo); + } + + @Test + public void testWithBodyMessage() { + GcfSpringBootBackgroundFunctionHandler handler = handler(FunctionMessageConfig.class); + + Foo foo = new Foo("foo"); + HashMap attributes = new HashMap<>(); + attributes.put("attribute", "value"); + + PubSubMessage psMessage = new PubSubMessage(GSON.toJson(foo), attributes, "id", "2020-02-01"); + handler.accept(psMessage, null); + + assertThat(FunctionMessageConfig.argument.getHeaders().get("messageId")).isEqualTo("id"); + assertThat(FunctionMessageConfig.argument.getHeaders().get("publishTime")).isEqualTo("2020-02-01"); + assertThat(FunctionMessageConfig.argument.getHeaders().get("attribute")).isEqualTo("value"); + assertThat(FunctionMessageConfig.argument.getPayload()).isEqualTo(foo); + } + + @After + public void close() { + if (this.handler != null) { + this.handler.close(); + } + } + + @Configuration + @Import({ContextFunctionCatalogAutoConfiguration.class}) + protected static class FunctionConfig { + static Foo argument; + + @Bean + public Consumer function() { + return (foo -> { + argument = foo; + }); + } + } + + @Configuration + @Import({ContextFunctionCatalogAutoConfiguration.class}) + protected static class FunctionMessageConfig { + static Message argument; + + @Bean + public Consumer> function() { + return (message -> { + argument = message; + }); + } + } +} + + + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java index 234d32458..d3cf7abc0 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerTests.java @@ -16,29 +16,13 @@ package org.springframework.cloud.function.adapter.gcloud; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; - +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.gson.Gson; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; - import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,6 +30,11 @@ import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; +import java.io.*; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -54,14 +43,13 @@ */ public class GcfSpringBootHttpRequestHandlerTests { HttpRequest request = Mockito.mock(HttpRequest.class); - HttpResponse response = Mockito.mock(HttpResponse.class); private GcfSpringBootHttpRequestHandler handler = null; public static final Gson GSON = new Gson(); GcfSpringBootHttpRequestHandler handler(Class config) { - GcfSpringBootHttpRequestHandler handler = - new GcfSpringBootHttpRequestHandler(config); + GcfSpringBootHttpRequestHandler handler = new GcfSpringBootHttpRequestHandler<>(config); + this.handler = handler; return handler; } @@ -124,6 +112,10 @@ public void close() { @Configuration @Import({ContextFunctionCatalogAutoConfiguration.class}) protected static class FunctionMessageBodyConfig { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } @Bean public Function, Message> function() { @@ -133,13 +125,18 @@ public Function, Message> function() { new Bar(foo.getPayload().getValue().toUpperCase()), headers); }); } - } @Configuration @Import({ContextFunctionCatalogAutoConfiguration.class}) protected static class FunctionMessageEchoReqParametersConfig { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean public Function, Message> function() { return (message -> { @@ -151,20 +148,24 @@ public Function, Message> function() { return new GenericMessage<>(new Bar("body"), headers); }); } - } @Configuration @Import({ContextFunctionCatalogAutoConfiguration.class}) protected static class FunctionMessageConsumerConfig { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + @Bean public Consumer> function() { return (foo -> { }); } } - private static class Foo { + public static class Foo { private String value; @@ -175,23 +176,27 @@ private static class Foo { this.value = value; } - public String lowercase() { - return this.value.toLowerCase(); - } + public String getValue() { + return this.value; + } - public String uppercase() { - return this.value.toUpperCase(); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - public String getValue() { - return this.value; - } - - public void setValue(String value) { - this.value = value; + if (o == null || getClass() != o.getClass()) { + return false; } + Foo foo = (Foo) o; + return Objects.equals(value, foo.value); + } + @Override + public int hashCode() { + return Objects.hash(value); } +} private static class Bar { From a422444aa3c651a72997ba22c307aa97a076b1bb Mon Sep 17 00:00:00 2001 From: dsolomakha Date: Thu, 26 Mar 2020 19:08:12 -0400 Subject: [PATCH 2/2] Base64 encoding, minor fixes --- ...bstractGcfHandler.java => GcfHandler.java} | 36 +- ...fSpringBootHttpRequestHandlerOriginal.java | 103 ++++++ ...> GcfSpringBootPubSubFunctionHandler.java} | 41 ++- .../adapter/gcloud/PubSubMessage.java | 28 +- ...ngBootHttpRequestHandlerOriginalTests.java | 313 ++++++++++++++++++ ... => GcfSpringBootPubSubFunctionTests.java} | 46 +-- .../function-sample-gcp/pom.xml | 2 + .../java/com/example/CloudFunctionMain.java | 11 +- 8 files changed, 541 insertions(+), 39 deletions(-) rename spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/{AbstractGcfHandler.java => GcfHandler.java} (66%) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginal.java rename spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/{GcfSpringBootBackgroundFunctionHandler.java => GcfSpringBootPubSubFunctionHandler.java} (50%) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginalTests.java rename spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/{GcfSpringBootBackgroundFunctionTests.java => GcfSpringBootPubSubFunctionTests.java} (72%) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfHandler.java similarity index 66% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfHandler.java index 467016923..5843a5f3b 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/AbstractGcfHandler.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfHandler.java @@ -1,28 +1,49 @@ +/* + * Copyright 2012-2019 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.cloud.function.adapter.gcloud; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; + import com.google.cloud.functions.Context; import com.google.cloud.functions.HttpResponse; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; + import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; import org.springframework.messaging.Message; -import reactor.core.publisher.Flux; -import java.util.*; +public class GcfHandler extends AbstractSpringFunctionAdapterInitializer { -public class AbstractGcfHandler extends AbstractSpringFunctionAdapterInitializer { - public AbstractGcfHandler(Class configurationClass) { + public GcfHandler(Class configurationClass) { super(configurationClass); init(); } - public AbstractGcfHandler() { + public GcfHandler() { super(); init(); } public void init() { - Thread.currentThread() - .setContextClassLoader(GcfSpringBootHttpRequestHandler.class.getClassLoader()); + Thread.currentThread().setContextClassLoader(GcfSpringBootHttpRequestHandler.class.getClassLoader()); initialize(null); } @@ -46,7 +67,6 @@ protected T result(Object input, Publisher output, HttpResponse resp) { return (T) result; } - private boolean isSingleValue(Object input) { return !(input instanceof Collection); } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginal.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginal.java new file mode 100644 index 000000000..9d070b080 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginal.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2019 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.cloud.function.adapter.gcloud; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import org.reactivestreams.Publisher; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; + +/** + * Implementation of HttpFunction for Google Cloud Function. + * + * @param input type + * @author Dmitry Solomakha + */ +public class GcfSpringBootHttpRequestHandlerOriginal extends GcfHandler implements HttpFunction { + + public GcfSpringBootHttpRequestHandlerOriginal(Class configurationClass) { + super(configurationClass); + } + + public GcfSpringBootHttpRequestHandlerOriginal() { + super(); + } + + @Autowired + private ObjectMapper mapper; + + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { + Publisher output = apply(extract(convert(httpRequest))); + BufferedWriter writer = httpResponse.getWriter(); + Object result = result(httpRequest, output, httpResponse); + if (returnsOutput()) { + writer.write(mapper.writeValueAsString(result)); + writer.flush(); + } + httpResponse.setStatusCode(200); + } + + private Object convert(HttpRequest event) throws IOException { + BufferedReader br = event.getReader(); + StringBuilder sb = new StringBuilder(); + + char[] buffer = new char[1024 * 4]; + int n; + while (-1 != (n = br.read(buffer))) { + sb.append(buffer, 0, n); + } + + String requestBody = sb.toString(); + if (functionAcceptsMessage()) { + return new GenericMessage<>(toOptionalIfEmpty(requestBody), getHeaders(event)); + } + return toOptionalIfEmpty(requestBody); + } + + private MessageHeaders getHeaders(HttpRequest event) { + Map headers = new HashMap(); + + if (event.getHeaders() != null) { + headers.putAll(event.getHeaders()); + } + if (event.getQueryParameters() != null) { + headers.putAll(event.getQueryParameters()); + } + if (event.getUri() != null) { + headers.put("path", event.getPath()); + } + + if (event.getMethod() != null) { + headers.put("httpMethod", event.getMethod()); + } + + return new MessageHeaders(headers); + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionHandler.java similarity index 50% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionHandler.java index cb687bcd4..bc4847160 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionHandler.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionHandler.java @@ -1,20 +1,42 @@ +/* + * Copyright 2012-2019 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.cloud.function.adapter.gcloud; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + import com.google.cloud.functions.BackgroundFunction; import com.google.cloud.functions.Context; +import reactor.core.publisher.Flux; + import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.GenericMessage; -import reactor.core.publisher.Flux; -import java.util.HashMap; -import java.util.Map; +import static java.nio.charset.StandardCharsets.UTF_8; -public class GcfSpringBootBackgroundFunctionHandler extends AbstractGcfHandler implements BackgroundFunction { - public GcfSpringBootBackgroundFunctionHandler(Class configurationClass) { +public class GcfSpringBootPubSubFunctionHandler extends GcfHandler + implements BackgroundFunction { + + public GcfSpringBootPubSubFunctionHandler(Class configurationClass) { super(configurationClass); } - public GcfSpringBootBackgroundFunctionHandler() { + public GcfSpringBootPubSubFunctionHandler() { super(); } @@ -24,10 +46,11 @@ public void accept(PubSubMessage pubSubMessage, Context context) { } private Object toMessageIfNeeded(PubSubMessage pubSubMessage) { + String data = new String(Base64.getDecoder().decode(pubSubMessage.getData()), UTF_8); if (functionAcceptsMessage()) { - return new GenericMessage<>(toOptionalIfEmpty(pubSubMessage.getData()), getHeaders(pubSubMessage)); + return new GenericMessage<>(toOptionalIfEmpty(data), getHeaders(pubSubMessage)); } - return toOptionalIfEmpty(pubSubMessage.getData()); + return toOptionalIfEmpty(data); } private Map getHeaders(PubSubMessage pubSubMessage) { @@ -47,5 +70,5 @@ private Map getHeaders(PubSubMessage pubSubMessage) { return new MessageHeaders(headers); } -} +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java index 05ce6a5e4..ffbfdedef 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcloud/PubSubMessage.java @@ -1,14 +1,37 @@ +/* + * Copyright 2012-2019 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.cloud.function.adapter.gcloud; import java.util.Map; -class PubSubMessage { +public class PubSubMessage { + String data; + Map attributes; + String messageId; + String publishTime; - public PubSubMessage(String data, Map attributes, String messageId, String publishTime) { + public PubSubMessage() { + } + + PubSubMessage(String data, Map attributes, String messageId, String publishTime) { this.data = data; this.attributes = attributes; this.messageId = messageId; @@ -46,4 +69,5 @@ public String getPublishTime() { public void setPublishTime(String publishTime) { this.publishTime = publishTime; } + } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginalTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginalTests.java new file mode 100644 index 000000000..29fea1466 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootHttpRequestHandlerOriginalTests.java @@ -0,0 +1,313 @@ +/* + * Copyright 2019-2019 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.cloud.function.adapter.gcloud; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import org.junit.After; +import org.junit.Test; +import org.mockito.Mockito; + +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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +/** + * @author Dmitry Solomakha + */ +public class GcfSpringBootHttpRequestHandlerOriginalTests { + + HttpRequest request = Mockito.mock(HttpRequest.class); + + private GcfSpringBootHttpRequestHandlerOriginal handler = null; + + public static final Gson GSON = new Gson(); + + GcfSpringBootHttpRequestHandlerOriginal handler(Class config) { + GcfSpringBootHttpRequestHandlerOriginal handler = new GcfSpringBootHttpRequestHandlerOriginal<>(config); + + this.handler = handler; + return handler; + } + + @Test + public void testWithBody() throws Exception { + GcfSpringBootHttpRequestHandlerOriginal handler = handler(FunctionMessageBodyConfig.class); + + StringReader foo = new StringReader(GSON.toJson(new Foo("foo"))); + when(request.getReader()).thenReturn(new BufferedReader(foo)); + + StringWriter writer = new StringWriter(); + handler.service(request, new HttpResponseImpl(new BufferedWriter(writer))); + + assertThat(GSON.fromJson(writer.toString(), Bar.class)).isEqualTo(new Bar("FOO")); + } + + @Test + public void testWithRequestParameters() throws Exception { + GcfSpringBootHttpRequestHandlerOriginal handler = handler(FunctionMessageEchoReqParametersConfig.class); + + when(request.getReader()).thenReturn(new BufferedReader(new StringReader(""))); + when(request.getUri()).thenReturn("http://localhost:8080/pathValue"); + when(request.getPath()).thenReturn("/pathValue"); + when(request.getHeaders()) + .thenReturn(Collections.singletonMap("test-header", Collections.singletonList("headerValue"))); + when(request.getMethod()).thenReturn("GET"); + + StringWriter writer = new StringWriter(); + HttpResponseImpl response = new HttpResponseImpl(new BufferedWriter(writer)); + handler.service(request, response); + + assertThat(response.statusCode).isEqualTo(200); + assertThat(response.headers.get("path")).containsExactly("/pathValue"); + assertThat(response.headers.get("test-header")).containsExactly("headerValue"); + assertThat(GSON.fromJson(writer.toString(), Bar.class)).isEqualTo(new Bar("body")); + } + + @Test + public void testWithEmptyBody() throws Exception { + GcfSpringBootHttpRequestHandlerOriginal handler = handler(FunctionMessageConsumerConfig.class); + + when(request.getReader()).thenReturn(new BufferedReader(new StringReader(""))); + + StringWriter writer = new StringWriter(); + HttpResponseImpl response = new HttpResponseImpl(new BufferedWriter(writer)); + handler.service(request, response); + + assertThat(response.statusCode).isEqualTo(200); + assertThat(writer.toString()).isEqualTo(""); + } + + @After + public void close() { + if (this.handler != null) { + this.handler.close(); + } + } + + @Configuration + @Import({ ContextFunctionCatalogAutoConfiguration.class }) + protected static class FunctionMessageBodyConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public Function, Message> function() { + return (foo -> { + Map headers = new HashMap<>(); + return new GenericMessage<>(new Bar(foo.getPayload().getValue().toUpperCase()), headers); + }); + } + + } + + @Configuration + @Import({ ContextFunctionCatalogAutoConfiguration.class }) + protected static class FunctionMessageEchoReqParametersConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public Function, Message> function() { + return (message -> { + Map headers = new HashMap<>(); + headers.put("path", message.getHeaders().get("path")); + headers.put("query", message.getHeaders().get("query")); + headers.put("test-header", message.getHeaders().get("test-header")); + headers.put("httpMethod", message.getHeaders().get("httpMethod")); + return new GenericMessage<>(new Bar("body"), headers); + }); + } + + } + + @Configuration + @Import({ ContextFunctionCatalogAutoConfiguration.class }) + protected static class FunctionMessageConsumerConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public Consumer> function() { + return (foo -> { + }); + } + + } + + public static class Foo { + + private String value; + + Foo() { + } + + Foo(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Foo foo = (Foo) o; + return Objects.equals(value, foo.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + } + + private static class Bar { + + private String value; + + Bar() { + } + + Bar(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Bar bar = (Bar) o; + return Objects.equals(getValue(), bar.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + } + + private static class HttpResponseImpl implements HttpResponse { + + int statusCode; + + String contentType; + + BufferedWriter writer; + + HttpResponseImpl(BufferedWriter writer) { + this.writer = writer; + } + + Map> headers = new HashMap<>(); + + @Override + public void setStatusCode(int code) { + statusCode = code; + } + + @Override + public void setStatusCode(int code, String message) { + statusCode = code; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public Optional getContentType() { + return Optional.ofNullable(contentType); + } + + @Override + public void appendHeader(String header, String value) { + headers.computeIfAbsent(header, x -> new ArrayList<>()).add(value); + } + + @Override + public Map> getHeaders() { + return headers; + } + + @Override + public OutputStream getOutputStream() throws IOException { + throw new RuntimeException("unsupported!"); + } + + @Override + public BufferedWriter getWriter() throws IOException { + return writer; + } + + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionTests.java similarity index 72% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionTests.java index 016b2bfde..21720e0f5 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootBackgroundFunctionTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/test/java/org/springframework/cloud/function/adapter/gcloud/GcfSpringBootPubSubFunctionTests.java @@ -16,32 +16,34 @@ package org.springframework.cloud.function.adapter.gcloud; +import java.util.Base64; +import java.util.HashMap; +import java.util.function.Consumer; + import com.google.gson.Gson; import org.junit.After; import org.junit.Test; + 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.HashMap; -import java.util.Map; -import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandlerTests.*; +import static org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandlerOriginalTests.Foo; /** * @author Dmitry Solomakha */ -public class GcfSpringBootBackgroundFunctionTests { - private GcfSpringBootBackgroundFunctionHandler handler = null; +public class GcfSpringBootPubSubFunctionTests { + + private GcfSpringBootPubSubFunctionHandler handler = null; + public static final Gson GSON = new Gson(); - GcfSpringBootBackgroundFunctionHandler handler(Class config) { - GcfSpringBootBackgroundFunctionHandler handler = new GcfSpringBootBackgroundFunctionHandler<>(config); + GcfSpringBootPubSubFunctionHandler handler(Class config) { + GcfSpringBootPubSubFunctionHandler handler = new GcfSpringBootPubSubFunctionHandler<>(config); this.handler = handler; return handler; @@ -49,10 +51,10 @@ GcfSpringBootBackgroundFunctionHandler handler(Class config) { @Test public void testWithBody() { - GcfSpringBootBackgroundFunctionHandler handler = handler(FunctionConfig.class); + GcfSpringBootPubSubFunctionHandler handler = handler(FunctionConfig.class); Foo foo = new Foo("foo"); - PubSubMessage psMessage = new PubSubMessage(GSON.toJson(foo), null, null, null); + PubSubMessage psMessage = new PubSubMessage(toBase64EncodedJson(foo), null, null, null); handler.accept(psMessage, null); assertThat(FunctionConfig.argument).isEqualTo(foo); @@ -60,13 +62,13 @@ public void testWithBody() { @Test public void testWithBodyMessage() { - GcfSpringBootBackgroundFunctionHandler handler = handler(FunctionMessageConfig.class); + GcfSpringBootPubSubFunctionHandler handler = handler(FunctionMessageConfig.class); Foo foo = new Foo("foo"); HashMap attributes = new HashMap<>(); attributes.put("attribute", "value"); - PubSubMessage psMessage = new PubSubMessage(GSON.toJson(foo), attributes, "id", "2020-02-01"); + PubSubMessage psMessage = new PubSubMessage(toBase64EncodedJson(foo), attributes, "id", "2020-02-01"); handler.accept(psMessage, null); assertThat(FunctionMessageConfig.argument.getHeaders().get("messageId")).isEqualTo("id"); @@ -75,6 +77,10 @@ public void testWithBodyMessage() { assertThat(FunctionMessageConfig.argument.getPayload()).isEqualTo(foo); } + private String toBase64EncodedJson(Foo foo) { + return Base64.getEncoder().encodeToString(GSON.toJson(foo).getBytes()); + } + @After public void close() { if (this.handler != null) { @@ -83,8 +89,9 @@ public void close() { } @Configuration - @Import({ContextFunctionCatalogAutoConfiguration.class}) + @Import({ ContextFunctionCatalogAutoConfiguration.class }) protected static class FunctionConfig { + static Foo argument; @Bean @@ -93,11 +100,13 @@ public Consumer function() { argument = foo; }); } + } @Configuration - @Import({ContextFunctionCatalogAutoConfiguration.class}) + @Import({ ContextFunctionCatalogAutoConfiguration.class }) protected static class FunctionMessageConfig { + static Message argument; @Bean @@ -106,8 +115,7 @@ public Consumer> function() { argument = message; }); } - } -} - + } +} diff --git a/spring-cloud-function-samples/function-sample-gcp/pom.xml b/spring-cloud-function-samples/function-sample-gcp/pom.xml index 5debf3b91..8bda6765b 100644 --- a/spring-cloud-function-samples/function-sample-gcp/pom.xml +++ b/spring-cloud-function-samples/function-sample-gcp/pom.xml @@ -28,6 +28,8 @@ 0.9.1 org.springframework.cloud.function.adapter.gcloud.GcfSpringBootHttpRequestHandler + + 8080 diff --git a/spring-cloud-function-samples/function-sample-gcp/src/main/java/com/example/CloudFunctionMain.java b/spring-cloud-function-samples/function-sample-gcp/src/main/java/com/example/CloudFunctionMain.java index dc5f213ab..41a692964 100644 --- a/spring-cloud-function-samples/function-sample-gcp/src/main/java/com/example/CloudFunctionMain.java +++ b/spring-cloud-function-samples/function-sample-gcp/src/main/java/com/example/CloudFunctionMain.java @@ -18,6 +18,9 @@ import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @@ -25,12 +28,18 @@ @SpringBootApplication public class CloudFunctionMain { + private final Logger logger = LoggerFactory.getLogger(CloudFunctionMain.class); + public static void main(String[] args) { SpringApplication.run(CloudFunctionMain.class, args); } @Bean public Function function() { - return value -> value.toUpperCase(); + return value -> { + logger.info("function was called with this argument: " + value); + return value.toUpperCase(); + }; } + }