From 591b3bf53111309f93a8a09e916c992951255474 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 19 Aug 2019 17:03:40 +0200 Subject: [PATCH] Fixed README generation and polished few tests --- README.adoc | 7 ++- docs/src/main/asciidoc/_intro.adoc | 20 +++++---- .../BeanFactoryAwareFunctionRegistry.java | 3 +- ...BeanFactoryAwareFunctionRegistryTests.java | 45 ++++++++++++++++++- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/README.adoc b/README.adoc index dc11e0a84..dc72f1137 100644 --- a/README.adoc +++ b/README.adoc @@ -45,9 +45,12 @@ https://www.reactive-streams.org/[Reactive Streams] `Publisher` from https://projectreactor.io/[Project Reactor]. The function can be accessed over HTTP or messaging. -*Features* +Spring Cloud Function has 4 main features: In the nutshell Spring Cloud Function provides the following features: +1. Wrappers for `@Beans` of type `Function`, `Consumer` and +`Supplier`, exposing them to the outside world as either HTTP +endpoints and/or message stream listeners/publishers with RabbitMQ, Kafka etc. * _Choice of programming styles - reactive, imperative or hybrid._ * _Function composition and adaptation (e.g., composing imperative functions with reactive)._ @@ -391,4 +394,4 @@ Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on t - `checkstyle.suppressions.file` - default suppressions. Please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` URL. - `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`. -IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources. +IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources. \ No newline at end of file diff --git a/docs/src/main/asciidoc/_intro.adoc b/docs/src/main/asciidoc/_intro.adoc index 12132de7f..422f51dd2 100644 --- a/docs/src/main/asciidoc/_intro.adoc +++ b/docs/src/main/asciidoc/_intro.adoc @@ -7,7 +7,7 @@ Spring Cloud Function is a project with the following high-level goals: It abstracts away all of the transport details and infrastructure, allowing the developer to keep all the familiar tools -and processes, and focus firmly on business logic. +and processes, and focus firmly on business logic. Here's a complete, executable, testable Spring Boot application (implementing a simple string manipulation): @@ -37,15 +37,17 @@ accessed over HTTP or messaging. Spring Cloud Function has 4 main features: +In the nutshell Spring Cloud Function provides the following features: 1. Wrappers for `@Beans` of type `Function`, `Consumer` and `Supplier`, exposing them to the outside world as either HTTP endpoints and/or message stream listeners/publishers with RabbitMQ, Kafka etc. -2. Compiling strings which are Java function bodies into bytecode, and -then turning them into `@Beans` that can be wrapped as above. - -3. Deploying a JAR file containing such an application context with an -isolated classloader, so that you can pack them together in a single -JVM. - -4. Adapters for https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-aws[AWS Lambda], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[Azure], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk[Apache OpenWhisk] and possibly other "serverless" service providers. \ No newline at end of file +* _Choice of programming styles - reactive, imperative or hybrid._ +* _Function composition and adaptation (e.g., composing imperative functions with reactive)._ +* _Support for reactive function with multiple inputs and outputs allowing merging, joining and other complex streaming operation to be handled by functions._ +* _Transparent type conversion of inputs and outputs._ +* _Packaging functions for deployments, specific to the target platform (e.g., Project Riff, AWS Lambda and more)_ +* _Adapters to expose function to the outside world as HTTP endpoints etc._ +* _Deploying a JAR file containing such an application context with an isolated classloader, so that you can pack them together in a single JVM._ +* _Compiling strings which are Java function bodies into bytecode, and then turning them into `@Beans` that can be wrapped as above._ +* _Adapters for https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-aws[AWS Lambda], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[Azure], https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk[Apache OpenWhisk] and possibly other "serverless" service providers._ \ No newline at end of file diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java index e6cf5de87..3ad8fd425 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java @@ -430,6 +430,7 @@ public class BeanFactoryAwareFunctionRegistry } } + // Outputs will be converted only if we're told how (via acceptedOutputMimeTypes), otherwise output returned as is. if (!ObjectUtils.isEmpty(this.acceptedOutputMimeTypes)) { result = result instanceof Publisher ? this.convertOutputPublisherIfNecessary((Publisher) result, this.acceptedOutputMimeTypes) @@ -462,7 +463,7 @@ public class BeanFactoryAwareFunctionRegistry } convertedValue = Tuples.fromArray(convertedInputArray); } - else { + else if (value != null) { List acceptedContentTypes = MimeTypeUtils.parseMimeTypes(acceptedOutputMimeTypes[0].toString()); convertedValue = acceptedContentTypes.stream() diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java index b07120ccf..7fd69c188 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java @@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -78,6 +79,28 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(messageResult.get(1).getPayload()).isEqualTo("\"UPPERCASEFLUX2\"".getBytes(StandardCharsets.UTF_8)); } + @Test + public void testConsumerFunction() { // function that returns Void, effectively a Consumer + FunctionCatalog catalog = this.configureCatalog(); + + Function consumerFunction = catalog.lookup("consumerFunction"); + assertThat(consumerFunction.apply("hello")).isNull(); + + Function, Void> consumerFunctionAsMessageA = catalog.lookup("consumerFunction"); + assertThat(consumerFunctionAsMessageA.apply(new GenericMessage("\"hello\"".getBytes()))).isNull(); + + Function, Void> consumerFunctionAsMessageB = catalog.lookup("consumerFunction", "application/json"); + assertThat(consumerFunctionAsMessageB.apply(new GenericMessage("\"hello\"".getBytes()))).isNull(); + } + + @Test + public void testMessageToPojoConversion() { + FunctionCatalog catalog = this.configureCatalog(); + Function, Person> uppercasePerson = catalog.lookup("uppercasePerson"); + Person person = uppercasePerson.apply(MessageBuilder.withPayload("{\"name\":\"bill\",\"id\":2}").build()); + assertThat(person.getName()).isEqualTo("BILL"); + } + /* * When invoking imperative function as reactive the rules are * - the input wrapper must match the output wrapper (e.g., or ) @@ -249,6 +272,13 @@ public class BeanFactoryAwareFunctionRegistryTests { @Configuration protected static class SampleFunctionConfiguration { + @Bean + public Function uppercasePerson() { + return person -> { + return new Person(person.getName().toUpperCase(), person.getId()); + }; + } + @Bean public Supplier numberword() { return () -> "one"; @@ -267,6 +297,14 @@ public class BeanFactoryAwareFunctionRegistryTests { return v -> v.toUpperCase(); } + @Bean + public Function consumerFunction() { + return v -> { + System.out.println("Value: " + v); + return null; + }; + } + @Bean public Function, Flux> uppercaseFlux() { return flux -> flux.map(v -> v.toUpperCase()); @@ -392,10 +430,13 @@ public class BeanFactoryAwareFunctionRegistryTests { } } - private static class Person { + public static class Person { private String name; private int id; - Person(String name, int id) { + public Person() { + + } + public Person(String name, int id) { this.name = name; this.id = id; }