From f4171cae161a9866472f2b1880ad05273ece7de5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 8 Nov 2021 15:26:11 +0100 Subject: [PATCH 01/41] GH-764 Add support for output header enrichemnt Resolves #764 --- .../main/asciidoc/spring-cloud-function.adoc | 42 +++++++++------ .../function/context/FunctionProperties.java | 48 +++++++++++++---- ...InputEnricher.java => HeaderEnricher.java} | 6 +-- .../catalog/SimpleFunctionRegistry.java | 33 ++++++++++-- ...pingTests.java => HeaderMappingTests.java} | 52 +++++++++++++++++-- 5 files changed, 146 insertions(+), 35 deletions(-) rename spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/{InputEnricher.java => HeaderEnricher.java} (93%) rename spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/{InputHeaderMappingTests.java => HeaderMappingTests.java} (77%) diff --git a/docs/src/main/asciidoc/spring-cloud-function.adoc b/docs/src/main/asciidoc/spring-cloud-function.adoc index e175e9a06..36bd0c7e4 100644 --- a/docs/src/main/asciidoc/spring-cloud-function.adoc +++ b/docs/src/main/asciidoc/spring-cloud-function.adoc @@ -258,10 +258,9 @@ due to the nature of the reactive functions which are invoked only once to pass is handled by the reactor, hence we can not access and/or rely on the routing instructions communicated via individual values (e.g., Message). -=== Input Enrichment +=== Input/Output Enrichment -There are often times when you need to modify or refine an incoming Message and to keep your code clean of non-functional concerns, and you don’t want to -do it inside of your business logic. +There are often times when you need to modify or refine an incoming or outgoing Message and to keep your code clean of non-functional concerns. You don’t want to do it inside of your business logic. You can always accomplish it via <>. Such approach provides several benefits: @@ -289,30 +288,39 @@ manually register it as a function before you can compose it with the business f But what if modifications (enrichments) you are trying to make are trivial as they are in the preceding example? Is there a simpler and more dynamic and configurable mechanism to accomplish the same? -Since version 3.1.3, the framework allows you to provide SpEL expression to enrich individual message headers. Let’s look at one of the tests as the example. +Since version 3.1.3, the framework allows you to provide SpEL expression to enrich individual message headers for both input going into function and +and output coming out of it. Let’s look at one of the tests as the example. [source, java] ---- @Test -public void testInputHeaderMappingPropertyWithoutIndex() throws Exception { +public void testMixedInputOutputHeaderMapping() throws Exception { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run( - "--spring.cloud.function.configuration.echo.input-header-mapping-expression.key1='hello1'", - "--spring.cloud.function.configuration.echo.input-header-mapping-expression.key2='hello2'", - "--spring.cloud.function.configuration.echo.input-header-mapping-expression.foo=headers.contentType")) { + "--logging.level.org.springframework.cloud.function=DEBUG", + "--spring.main.lazy-initialization=true", + "--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut1='hello1'", + "--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut2=headers.contentType", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key1=headers.path.split('/')[0]", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key2=headers.path.split('/')[1]", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key3=headers.path")) { - FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); - FunctionInvocationWrapper function = functionCatalog.lookup("echo"); - function.apply(MessageBuilder.withPayload("helo") - .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build()); - } + FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); + FunctionInvocationWrapper function = functionCatalog.lookup("split"); + Message result = (Message) function.apply(MessageBuilder.withPayload("helo") + .setHeader(MessageHeaders.CONTENT_TYPE, "application/json") + .setHeader("path", "foo/bar/baz").build()); + assertThat(result.getHeaders().containsKey("keyOut1")).isTrue(); + assertThat(result.getHeaders().get("keyOut1")).isEqualTo("hello1"); + assertThat(result.getHeaders().containsKey("keyOut2")).isTrue(); + assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json"); + } } ---- -Here you see a property called `input-header-mapping-expression` preceded by the name of the function (i.e., `echo`) and followed by the name of the -message header key you want to set and the value as SpEL expression. The first two expressions (for 'key1' and 'key2') are literal SpEL expressions enclosed in -single quotes, effectively setting 'key1' to value `hello1` and 'key2' to value `hello2`. The third one will map Message header ‘foo’ to the value of the -current ‘contentType’ header. +Here you see a properties called `input-header-mapping-expression` and `output-header-mapping-expression` preceded by the name of the function (i.e., `split`) and followed by the name of the message header key you want to set and the value as SpEL expression. The first expression (for 'keyOut1') is literal SpEL expressions enclosed in single quotes, effectively setting 'keyOut1' to value `hello1`. The `keyOut2` is set to the value of existing 'contentType' header. + +You can also observe some interesting features in the input header mapping where we actually splitting a value of the existing header 'path', setting individual values of key1 and key2 to the values of split elements based on the index. NOTE: if for whatever reason the provided expression evaluation fails, the execution of the function will proceed as if nothing ever happen. However you will see the WARN message in your logs informing you about it diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java index 5ca33bd4e..12f49a1e8 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java @@ -26,6 +26,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; /** * @@ -88,16 +89,34 @@ public class FunctionProperties implements EnvironmentAware, ApplicationContextA String propertyX = "spring.cloud.function.configuration." + entry.getKey() + ".input-header-mapping-expression."; String propertyY = "spring.cloud.function.configuration." + entry.getKey() + ".inputHeaderMappingExpression."; Map headerMapping = entry.getValue().getInputHeaderMappingExpression(); - for (Object k : headerMapping.keySet()) { - if (this.environment.containsProperty(propertyX + k) || this.environment.containsProperty(propertyY + k)) { - Map originalMapping = entry.getValue().getInputHeaderMappingExpression(); - Map current = entry.getValue().getInputHeaderMappingExpression(); - if (current.containsKey("0")) { - ((Map) current.get("0")).put(k, headerMapping.get(k)); + if (!CollectionUtils.isEmpty(headerMapping)) { + for (Object k : headerMapping.keySet()) { + if (this.environment.containsProperty(propertyX + k) || this.environment.containsProperty(propertyY + k)) { + Map current = entry.getValue().getInputHeaderMappingExpression(); + if (current.containsKey("0")) { + ((Map) current.get("0")).put(k, headerMapping.get(k)); + } + else { + entry.getValue().setInputHeaderMappingExpression(Collections.singletonMap("0", current)); + break; + } } - else { - entry.getValue().setInputHeaderMappingExpression(Collections.singletonMap("0", originalMapping)); - break; + } + } + propertyX = "spring.cloud.function.configuration." + entry.getKey() + ".output-header-mapping-expression."; + propertyY = "spring.cloud.function.configuration." + entry.getKey() + ".outputHeaderMappingExpression."; + headerMapping = entry.getValue().getOutputHeaderMappingExpression(); + if (!CollectionUtils.isEmpty(headerMapping)) { + for (Object k : headerMapping.keySet()) { + if (this.environment.containsProperty(propertyX + k) || this.environment.containsProperty(propertyY + k)) { + Map current = entry.getValue().getOutputHeaderMappingExpression(); + if (current.containsKey("0")) { + ((Map) current.get("0")).put(k, headerMapping.get(k)); + } + else { + entry.getValue().setOutputHeaderMappingExpression(Collections.singletonMap("0", current)); + break; + } } } } @@ -149,6 +168,8 @@ public class FunctionProperties implements EnvironmentAware, ApplicationContextA private Map inputHeaderMappingExpression; + private Map outputHeaderMappingExpression; + public Map getInputHeaderMappingExpression() { return inputHeaderMappingExpression; } @@ -157,5 +178,14 @@ public class FunctionProperties implements EnvironmentAware, ApplicationContextA this.inputHeaderMappingExpression = inputHeaderMappingExpression; } + public Map getOutputHeaderMappingExpression() { + return outputHeaderMappingExpression; + } + + public void setOutputHeaderMappingExpression( + Map outputHeaderMappingExpression) { + this.outputHeaderMappingExpression = outputHeaderMappingExpression; + } + } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InputEnricher.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/HeaderEnricher.java similarity index 93% rename from spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InputEnricher.java rename to spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/HeaderEnricher.java index 7121a24a9..ff8e1ebe7 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InputEnricher.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/HeaderEnricher.java @@ -42,9 +42,9 @@ import org.springframework.util.Assert; * @since 3.1.3 * */ -class InputEnricher implements Function { +class HeaderEnricher implements Function { - protected Log logger = LogFactory.getLog(InputEnricher.class); + protected Log logger = LogFactory.getLog(HeaderEnricher.class); private final Map> headerExpressions; @@ -53,7 +53,7 @@ class InputEnricher implements Function { private final StandardEvaluationContext evalContext = new StandardEvaluationContext(); @SuppressWarnings({ "rawtypes", "unchecked" }) - InputEnricher(Map headerExpressions, @Nullable BeanResolver beanResolver) { + HeaderEnricher(Map headerExpressions, @Nullable BeanResolver beanResolver) { Assert.notEmpty(headerExpressions, "'headerExpressions' must not be null or empty"); this.headerExpressions = headerExpressions; this.evalContext.addPropertyAccessor(new MapAccessor()); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 019d0c53a..863aa30f9 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -304,6 +304,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect composedFunction = (FunctionInvocationWrapper) composedFunction.andThen((Function) andThenFunction); } composedFunction = this.enrichInputIfNecessary(composedFunction); + composedFunction = this.enrichOutputIfNecessary(composedFunction); if (composedFunction.isSingleton) { this.wrappedFunctionDefinitions.put(composedFunction.functionDefinition, composedFunction); } @@ -329,8 +330,32 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect BeanFactoryResolver beanResolver = this.functionProperties.getApplicationContext() != null ? new BeanFactoryResolver(this.functionProperties.getApplicationContext()) : null; - InputEnricher enricher = new InputEnricher(configuration.getInputHeaderMappingExpression(), beanResolver); - FunctionInvocationWrapper w = new FunctionInvocationWrapper("headerEnricher", enricher, Message.class, Message.class); + HeaderEnricher enricher = new HeaderEnricher(configuration.getInputHeaderMappingExpression(), beanResolver); + FunctionInvocationWrapper w = new FunctionInvocationWrapper("inputHeaderEnricher", enricher, Message.class, Message.class); + composedFunction = (FunctionInvocationWrapper) w.andThen((Function) composedFunction); + composedFunction.functionDefinition = functionDefinition; + } + } + } + return composedFunction; + } + + private FunctionInvocationWrapper enrichOutputIfNecessary(FunctionInvocationWrapper composedFunction) { + if (this.functionProperties == null) { + return composedFunction; + } + String functionDefinition = composedFunction.getFunctionDefinition(); + Map configurationProperties = this.functionProperties.getConfiguration(); + if (!CollectionUtils.isEmpty(configurationProperties)) { + FunctionConfigurationProperties configuration = configurationProperties + .get(functionDefinition.replace("|", "").replace(",", "")); + if (configuration != null) { + if (!CollectionUtils.isEmpty(configuration.getOutputHeaderMappingExpression())) { + BeanFactoryResolver beanResolver = this.functionProperties.getApplicationContext() != null + ? new BeanFactoryResolver(this.functionProperties.getApplicationContext()) + : null; + HeaderEnricher enricher = new HeaderEnricher(configuration.getOutputHeaderMappingExpression(), beanResolver); + FunctionInvocationWrapper w = new FunctionInvocationWrapper("outputHeaderEnricher", enricher, Message.class, Message.class); composedFunction = (FunctionInvocationWrapper) w.andThen((Function) composedFunction); composedFunction.functionDefinition = functionDefinition; } @@ -1050,7 +1075,9 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect if (this.isWrapConvertedInputInMessage(convertedInput)) { convertedInput = MessageBuilder.withPayload(convertedInput).build(); } - Assert.notNull(convertedInput, () -> "Failed to convert input: " + input + " to " + type); + + Object finalInput = input; + Assert.notNull(convertedInput, () -> "Failed to convert input: " + finalInput + " to " + type); return convertedInput; } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/InputHeaderMappingTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java similarity index 77% rename from spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/InputHeaderMappingTests.java rename to spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java index 0467f16e7..16eaef369 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/InputHeaderMappingTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 2021-2021 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. @@ -33,8 +33,8 @@ import org.springframework.messaging.support.MessageBuilder; import static org.assertj.core.api.Assertions.assertThat; -//NOTE!!! assertions for all tests are in 'echo' function since we're validating what's coming into it. -public class InputHeaderMappingTests { +//NOTE!!! assertions for input in all tests are in 'echo' function since we're validating what's coming into it. +public class HeaderMappingTests { @Test public void testErrorWarnAndContinue() throws Exception { @@ -173,6 +173,52 @@ public class InputHeaderMappingTests { } } + @SuppressWarnings("unchecked") + @Test + public void testOutputHeaderMapping() throws Exception { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run( + "--logging.level.org.springframework.cloud.function=DEBUG", + "--spring.main.lazy-initialization=true", + "--spring.cloud.function.configuration.foo.output-header-mapping-expression.key1='hello1'", + "--spring.cloud.function.configuration.foo.output-header-mapping-expression.key2=headers.contentType")) { + + FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); + FunctionInvocationWrapper function = functionCatalog.lookup("foo"); + Message result = (Message) function.apply(MessageBuilder.withPayload("helo") + .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build()); + assertThat(result.getHeaders().containsKey("key1")).isTrue(); + assertThat(result.getHeaders().get("key1")).isEqualTo("hello1"); + assertThat(result.getHeaders().containsKey("key2")).isTrue(); + assertThat(result.getHeaders().get("key2")).isEqualTo("application/json"); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testMixedInputOutputHeaderMapping() throws Exception { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run( + "--logging.level.org.springframework.cloud.function=DEBUG", + "--spring.main.lazy-initialization=true", + "--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut1='hello1'", + "--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut2=headers.contentType", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key1=headers.path.split('/')[0]", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key2=headers.path.split('/')[1]", + "--spring.cloud.function.configuration.split.input-header-mapping-expression.key3=headers.path")) { + + FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); + FunctionInvocationWrapper function = functionCatalog.lookup("split"); + Message result = (Message) function.apply(MessageBuilder.withPayload("helo") + .setHeader(MessageHeaders.CONTENT_TYPE, "application/json") + .setHeader("path", "foo/bar/baz").build()); + assertThat(result.getHeaders().containsKey("keyOut1")).isTrue(); + assertThat(result.getHeaders().get("keyOut1")).isEqualTo("hello1"); + assertThat(result.getHeaders().containsKey("keyOut2")).isTrue(); + assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json"); + } + } + @EnableAutoConfiguration @Configuration protected static class SampleFunctionConfiguration { From 0cfb2b413fdbc41325e10a9ec07ba8de5713b993 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 8 Nov 2021 16:16:39 +0100 Subject: [PATCH 02/41] Initial round of deprecation rmovals --- .../context/FunctionRegistration.java | 30 -- .../AbstractComposableFunctionRegistry.java | 439 ------------------ .../context/catalog/MessageFunction.java | 106 ----- .../context/catalog/MessageSupplier.java | 63 --- .../context/config/FunctionContextUtils.java | 15 - .../context/message/MessageUtils.java | 7 - .../context/FunctionRegistrationTests.java | 58 --- .../context/catalog/MessageConsumerTests.java | 49 -- .../context/catalog/MessageFunctionTests.java | 113 ----- .../context/catalog/MessageSupplierTests.java | 79 ---- .../cloud/function/core/FluxConsumer.java | 48 -- .../cloud/function/core/FluxFunction.java | 47 -- .../cloud/function/core/FluxSupplier.java | 71 --- .../function/core/FluxToMonoFunction.java | 52 --- .../cloud/function/core/FluxWrapper.java | 30 -- .../cloud/function/core/FluxedConsumer.java | 50 -- .../cloud/function/core/FluxedFunction.java | 47 -- .../core/FunctionFactoryMetadata.java | 34 -- .../cloud/function/core/IsolatedSupplier.java | 57 --- .../cloud/function/core/MonoSupplier.java | 54 --- .../function/core/MonoToFluxFunction.java | 47 -- .../cloud/function/core/WrappedFunction.java | 55 --- 22 files changed, 1551 deletions(-) delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/AbstractComposableFunctionRegistry.java delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageFunction.java delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageSupplier.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionRegistrationTests.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageConsumerTests.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageFunctionTests.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageSupplierTests.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxConsumer.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxFunction.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxSupplier.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxToMonoFunction.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxWrapper.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedConsumer.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedFunction.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FunctionFactoryMetadata.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedSupplier.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoSupplier.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoToFluxFunction.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/WrappedFunction.java diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java index 536ce9ac2..c579273dd 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java @@ -35,14 +35,6 @@ import reactor.core.publisher.Mono; import org.springframework.beans.factory.BeanNameAware; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.config.RoutingFunction; -import org.springframework.cloud.function.core.FluxConsumer; -import org.springframework.cloud.function.core.FluxFunction; -import org.springframework.cloud.function.core.FluxSupplier; -import org.springframework.cloud.function.core.FluxToMonoFunction; -import org.springframework.cloud.function.core.FluxedConsumer; -import org.springframework.cloud.function.core.FluxedFunction; -import org.springframework.cloud.function.core.MonoSupplier; -import org.springframework.cloud.function.core.MonoToFluxFunction; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -193,28 +185,6 @@ public class FunctionRegistration implements BeanNameAware { S target = (S) this.target; result = new FunctionRegistration(target); result.type(this.type.getType()); - - if (!this.type.isWrapper()) { - target = target instanceof Supplier - ? (S) new FluxSupplier((Supplier) target) - : target instanceof Function - ? (S) new FluxFunction((Function) target) - : (S) new FluxConsumer((Consumer) target); - } - else if (Mono.class.isAssignableFrom(this.type.getOutputWrapper())) { - target = target instanceof Supplier - ? (S) new MonoSupplier((Supplier) target) - : (S) new FluxToMonoFunction((Function) target); - } - else if (Mono.class.isAssignableFrom(this.type.getInputWrapper())) { - target = (S) new MonoToFluxFunction((Function) target); - } - else if (target instanceof Consumer) { - target = (S) new FluxedConsumer((Consumer) target); - } - else if (target instanceof Function) { - target = (S) new FluxedFunction((Function) target); - } result = result.target(target).names(this.names) .type(result.type.wrap(Flux.class)).properties(this.properties); } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/AbstractComposableFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/AbstractComposableFunctionRegistry.java deleted file mode 100644 index f0b934c9f..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/AbstractComposableFunctionRegistry.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import reactor.core.publisher.Flux; - -import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionRegistry; -import org.springframework.cloud.function.context.FunctionType; -import org.springframework.cloud.function.context.config.RoutingFunction; -import org.springframework.cloud.function.core.FluxToMonoFunction; -import org.springframework.cloud.function.core.IsolatedConsumer; -import org.springframework.cloud.function.core.IsolatedFunction; -import org.springframework.cloud.function.core.IsolatedSupplier; -import org.springframework.cloud.function.core.MonoToFluxFunction; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * Base implementation of {@link FunctionRegistry} which supports function composition - * during lookups. For example if this registry contains function 'a' and 'b' you can - * compose them into a single function by simply piping two names together during the - * lookup {@code this.lookup(Function.class, "a|b")}. - * - * Comma ',' is also supported as composition delimiter (e.g., {@code "a,b"}). - * - * @author Oleg Zhurakousky - * @author Dave Syer - * @since 2.1 - * - */ -public abstract class AbstractComposableFunctionRegistry implements FunctionRegistry, - ApplicationEventPublisherAware, EnvironmentAware { - - private final Map functions = new ConcurrentHashMap<>(); - - private final Map names = new ConcurrentHashMap<>(); - - private final Map types = new ConcurrentHashMap<>(); - - private Environment environment = new StandardEnvironment(); - - protected ApplicationEventPublisher applicationEventPublisher; - - @SuppressWarnings("unchecked") - @Override - public T lookup(Class type, String name) { - String functionDefinitionName = !StringUtils.hasText(name) - && this.environment.containsProperty("spring.cloud.function.definition") - ? this.environment.getProperty("spring.cloud.function.definition") - : name; - return (T) this.doLookup(type, functionDefinitionName); - } - - @SuppressWarnings("serial") - @Override - public Set getNames(Class type) { - if (type == null) { - return new HashSet(getSupplierNames()) { - { - addAll(getFunctionNames()); - } - }; - } - if (Supplier.class.isAssignableFrom(type)) { - return this.getSupplierNames(); - } - if (Function.class.isAssignableFrom(type)) { - return this.getFunctionNames(); - } - return Collections.emptySet(); - } - - /** - * Returns the names of available Suppliers. - * @return immutable {@link Set} of available {@link Supplier} names. - */ - public Set getSupplierNames() { - return this.functions.entrySet().stream() - .filter(entry -> entry.getValue() instanceof Supplier) - .map(entry -> entry.getKey()) - .collect(Collectors.toSet()); - } - - /** - * Returns the names of available Functions. - * @return immutable {@link Set} of available {@link Function} names. - */ - public Set getFunctionNames() { - return this.functions.entrySet().stream() - .filter(entry -> !(entry.getValue() instanceof Supplier)) - .map(entry -> entry.getKey()) - .collect(Collectors.toSet()); - } - - public boolean hasSuppliers() { - return !CollectionUtils.isEmpty(getSupplierNames()); - } - - public boolean hasFunctions() { - return !CollectionUtils.isEmpty(getFunctionNames()); - } - - /** - * The size of this catalog, which is the count of all Suppliers, - * Function and Consumers currently registered. - * - * @return the count of all Suppliers, Function and Consumers currently registered. - */ - @Override - public int size() { - return this.functions.size(); - } - - public FunctionType getFunctionType(String name) { - return this.types.get(name); - } - - /** - * A reverse lookup where one can determine the actual name of the function reference. - * @param function should be an instance of {@link Supplier}, {@link Function} or - * {@link Consumer}; - * @return the name of the function or null. - */ - public String lookupFunctionName(Object function) { - return this.names.containsKey(function) ? this.names.get(function) : null; - } - - @Override - public void setApplicationEventPublisher( - ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - - public FunctionRegistration getRegistration(Object function) { - String functionName = function == null ? null - : this.lookupFunctionName(function); - if (StringUtils.hasText(functionName)) { - FunctionRegistration registration = new FunctionRegistration( - function, functionName); - FunctionType functionType = this.findType(registration, functionName); - return registration.type(functionType.getType()); - } - return null; - } - - @Override - public void register(FunctionRegistration functionRegistration) { - Assert.notEmpty(functionRegistration.getNames(), - "'registration' must contain at least one name before it is registered in catalog."); - register(functionRegistration, functionRegistration.getNames().iterator().next()); - } - - - - /** - * Registers function wrapped by the provided FunctionRegistration with - * this FunctionRegistry. - * - * @param registration instance of {@link FunctionRegistration} - * @param key the name of the function - */ - protected void register(FunctionRegistration registration, String key) { - Object target = registration.getTarget(); - if (registration.getType() != null) { - this.addType(key, registration.getType()); - } - else { - FunctionType functionType = findType(registration, key); - if (functionType == null) { - return; // TODO fixme - } - this.addType(key, functionType); - registration.type(functionType.getType()); - } - Class type; - registration = isolated(registration).wrap(); - target = registration.getTarget(); - if (target instanceof Supplier) { - type = Supplier.class; - for (String name : registration.getNames()) { - this.addSupplier(name, (Supplier) registration.getTarget()); - } - } - else if (target instanceof Function) { - type = Function.class; - for (String name : registration.getNames()) { - this.addFunction(name, (Function) registration.getTarget()); - } - } - else { - return; - } - this.addName(registration.getTarget(), key); - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent(new FunctionRegistrationEvent( - registration.getTarget(), type, registration.getNames())); - } - } - - protected FunctionType findType(FunctionRegistration functionRegistration, String name) { - return functionRegistration.getType() != null - ? functionRegistration.getType() - : this.getFunctionType(name); - } - - - protected void addSupplier(String name, Supplier supplier) { - this.functions.put(name, supplier); - } - - protected void addFunction(String name, Function function) { - this.functions.put(name, function); - } - - protected void addType(String name, FunctionType functionType) { - this.types.computeIfAbsent(name, str -> functionType); - } - - protected void addName(Object function, String name) { - this.names.put(function, name); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private FunctionRegistration isolated(FunctionRegistration input) { - FunctionRegistration registration = (FunctionRegistration) input; - Object target = registration.getTarget(); - boolean isolated = getClass().getClassLoader() != target.getClass() - .getClassLoader(); - if (isolated) { - if (target instanceof Supplier && isolated) { - target = new IsolatedSupplier((Supplier) target); - } - else if (target instanceof Function) { - target = new IsolatedFunction((Function) target); - } - else if (target instanceof Consumer) { - target = new IsolatedConsumer((Consumer) target); - } - } - - registration.target(target); - return registration; - } - - private Object compose(String name, Map lookup) { - - name = name.replaceAll(",", "|").trim(); - Object composedFunction = null; - - if (lookup.containsKey(name)) { - composedFunction = lookup.get(name); - } - else if (name.equals("") && lookup.size() >= 1 && lookup.size() <= 2) { // we may have RoutingFunction function - String functionName = lookup.keySet().stream() - .filter(fName -> !fName.equals(RoutingFunction.FUNCTION_NAME)) - .findFirst().orElseGet(() -> null); - composedFunction = lookup.get(functionName); - } - else { - String[] stages = StringUtils.delimitedListToStringArray(name, "|"); - - AtomicBoolean supplierPresent = new AtomicBoolean(); - List> composableFunctions = Stream.of(stages) - .map(funcName -> find(funcName, supplierPresent.get())) - .filter(x -> x != null) - .peek(f -> supplierPresent.set(f.getTarget() instanceof Supplier)) - .collect(Collectors.toList()); - FunctionRegistration composedRegistration = composableFunctions - .stream().reduce((a, z) -> composeFunctions(a, z)) - .orElseGet(() -> null); - - if (composedRegistration != null - && composedRegistration.getTarget() != null - && !this.types.containsKey(name)) { - - composedFunction = composedRegistration.getTarget(); - this.addType(name, composedRegistration.getType()); - this.addName(composedFunction, name); - if (composedFunction instanceof Function || composedFunction instanceof Consumer) { - this.addFunction(name, (Function) composedFunction); - } - else if (composedFunction instanceof Supplier) { - this.addSupplier(name, (Supplier) composedFunction); - } - } - - } - - return composedFunction; - } - - private FunctionRegistration find(String name, boolean supplierFound) { - Object result = this.functions.get(name); - if (result == null && !StringUtils.hasText(name)) { - if (supplierFound && this.getFunctionNames().size() == 1) { - result = this.functions.get(this.getFunctionNames().iterator().next()); - } - else if (!supplierFound && this.getSupplierNames().size() == 1) { - result = this.functions.get(this.getSupplierNames().iterator().next()); - } - } - - return getRegistration(result); - } - - @SuppressWarnings("unchecked") - private FunctionRegistration composeFunctions(FunctionRegistration aReg, - FunctionRegistration bReg) { - FunctionType aType = aReg.getType(); - FunctionType bType = bReg.getType(); - Object a = aReg.getTarget(); - Object b = bReg.getTarget(); - if (aType != null && bType != null) { - if (aType.isMessage() && !bType.isMessage()) { - bType = bType.message(); - b = message(b); - } - } - Object composedFunction = null; -// if (a instanceof Supplier && b instanceof Function) { -// Supplier> supplier = (Supplier>) a; -// if (b instanceof FluxConsumer) { -// if (supplier instanceof FluxSupplier) { -// FluxConsumer fConsumer = ((FluxConsumer) b); -// composedFunction = (Supplier>) () -> Mono.from( -// supplier.get().compose(v -> fConsumer.apply(supplier.get()))); -// } -// else { -// throw new IllegalStateException( -// "The provided supplier is finite (i.e., already composed with Consumer) " -// + "therefore it can not be composed with another consumer"); -// } -// } -// else { -// Function function = (Function) b; -// composedFunction = (Supplier) () -> function -// .apply(supplier.get()); -// } -// } -// else - if (a instanceof Function && b instanceof Function) { - Function function1 = (Function) a; - Function function2 = (Function) b; - if (function1 instanceof FluxToMonoFunction) { - if (function2 instanceof MonoToFluxFunction) { - composedFunction = function1.andThen(function2); - } - else { - throw new IllegalStateException( - "The provided function is finite (i.e., returns Mono) " - + "therefore it can *only* be composed with compatible function (i.e., Function"); - } - } - else if (function2 instanceof FluxToMonoFunction) { - composedFunction = new FluxToMonoFunction( - ((Function, Flux>) a).andThen( - ((FluxToMonoFunction) b).getTarget())); - } - else { - composedFunction = function1.andThen(function2); - } - } - else if (a instanceof Function && b instanceof Consumer) { - Function function = (Function) a; - Consumer consumer = (Consumer) b; - composedFunction = (Consumer) v -> consumer.accept(function.apply(v)); - } - else { - throw new IllegalArgumentException(String - .format("Could not compose %s and %s", a.getClass(), b.getClass())); - } - String name = aReg.getNames().iterator().next() + "|" - + bReg.getNames().iterator().next(); - return new FunctionRegistration<>(composedFunction, name) - .type(FunctionType.compose(aType, bType)); - } - - private Object message(Object input) { - if (input instanceof Supplier) { - return new MessageSupplier((Supplier) input); - } - if (input instanceof Consumer) { - return new MessageConsumer((Consumer) input); - } - if (input instanceof Function) { - return new MessageFunction((Function) input); - } - return input; - } - - private Object doLookup(Class type, String name) { - Object function = this.compose(name, this.functions); - if (function != null && type != null && !type.isAssignableFrom(function.getClass())) { - function = null; - } - return function; - } - -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageFunction.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageFunction.java deleted file mode 100644 index 6650e07d0..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageFunction.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.function.core.FluxConsumer; -import org.springframework.cloud.function.core.FluxFunction; -import org.springframework.cloud.function.core.FluxToMonoFunction; -import org.springframework.cloud.function.core.FluxedFunction; -import org.springframework.cloud.function.core.MonoToFluxFunction; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.MessageBuilder; - -/** - * @author Dave Syer - * @since 2.1 - */ -public class MessageFunction - implements Function, Publisher>> { - - private final Function delegate; - - public MessageFunction(Function delegate) { - this.delegate = delegate; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - public Publisher> apply(Publisher input) { - Flux incomingFlux = Flux.from(input); - Flux> flux = incomingFlux.map(value -> { - if (!(value instanceof Message)) { - return MessageBuilder.withPayload(value).build(); - } - return (Message) value; - }); - - if (this.delegate instanceof FluxFunction) { - Function target = (Function) ((FluxFunction) this.delegate) - .getTarget(); - return flux.map( - value -> MessageBuilder.withPayload(target.apply(value.getPayload())) - .copyHeaders(value.getHeaders()).build()); - } - if (this.delegate instanceof MonoToFluxFunction) { - Function, Flux> target = ((MonoToFluxFunction) this.delegate) - .getTarget(); - return flux.next() - .flatMapMany(value -> target.apply(Mono.just(value.getPayload())) - .map(object -> MessageBuilder.withPayload(object) - .copyHeaders(value.getHeaders()).build())); - } - if (this.delegate instanceof FluxToMonoFunction) { - Function, Mono> target = ((FluxToMonoFunction) this.delegate) - .getTarget(); - AtomicReference headers = new AtomicReference<>(); - return target.apply(flux.map(messsage -> { - headers.set(messsage.getHeaders()); - return messsage.getPayload(); - })).map(payload -> MessageBuilder.withPayload(payload) - .copyHeaders(headers.get()).build()); - } - if (this.delegate instanceof FluxConsumer) { - FluxConsumer target = ((FluxConsumer) this.delegate); - AtomicReference headers = new AtomicReference<>(); - Mono mapped = target.apply(flux.map(messsage -> { - headers.set(messsage.getHeaders()); - return messsage.getPayload(); - })); - return mapped.map(value -> MessageBuilder.createMessage(null, headers.get())); - } - - // TODO: cover the case that delegate is actually Function - if (this.delegate instanceof FluxedFunction) { - Function, Flux> target = ((FluxedFunction) this.delegate); - return (Flux) flux.map(value -> ((Message) value).getPayload()).transform(target); - } - Function function = this.delegate; - return flux.map( - value -> { - return MessageBuilder.withPayload(function.apply(value.getPayload())) - .copyHeaders(value.getHeaders()).build(); - }); - } -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageSupplier.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageSupplier.java deleted file mode 100644 index 999a3271b..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/MessageSupplier.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.function.Supplier; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.function.core.FluxSupplier; -import org.springframework.cloud.function.core.MonoSupplier; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -/** - * @author Dave Syer - */ -public class MessageSupplier implements Supplier>> { - - private Supplier delegate; - - public MessageSupplier(Supplier delegate) { - this.delegate = delegate; - } - - @Override - public Publisher> get() { - if (this.delegate instanceof FluxSupplier) { - return ((Flux) this.delegate.get()) - .map(value -> MessageBuilder.withPayload(value).build()); - } - if (this.delegate instanceof MonoSupplier) { - return ((Mono) this.delegate.get()) - .map(value -> MessageBuilder.withPayload(value).build()); - } - Object product = this.delegate.get(); - if (product instanceof Publisher) { - return Flux.from((Publisher) product) - .map(value -> MessageBuilder.withPayload(value).build()); - } - if (product instanceof Iterable) { - return Flux.fromIterable((Iterable) product) - .map(value -> MessageBuilder.withPayload(value).build()); - } - return Mono.just(MessageBuilder.withPayload(product).build()); - } - -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java index 1665b9d81..af3313f93 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java @@ -29,7 +29,6 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.cloud.function.core.FunctionFactoryMetadata; import org.springframework.context.annotation.ScannedGenericBeanDefinition; import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; @@ -84,20 +83,6 @@ public abstract class FunctionContextUtils { if (type != null) { param = type.getType(); } - else { - Class beanClass = definition.hasBeanClass() ? definition.getBeanClass() : null; - if (beanClass != null - && !FunctionFactoryMetadata.class.isAssignableFrom(beanClass)) { - param = beanClass; - } - else { - Object bean = registry.getBean(actualName); - // could be FunctionFactoryMetadata. . . TODO investigate and fix - if (bean instanceof FunctionFactoryMetadata) { - param = ((FunctionFactoryMetadata) bean).getFactoryMethod().getGenericReturnType(); - } - } - } } return param; } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java index 2c6b3c7bd..6b7aa1140 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.cloud.function.core.FluxWrapper; import org.springframework.cloud.function.core.Isolated; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; @@ -61,9 +60,6 @@ public abstract class MessageUtils { */ public static Object create(Object handler, Object payload, Map headers) { - if (handler instanceof FluxWrapper) { - handler = ((FluxWrapper) handler).getTarget(); - } if (payload instanceof Message) { headers = new HashMap<>(headers); headers.putAll(((Message) payload).getHeaders()); @@ -93,9 +89,6 @@ public abstract class MessageUtils { * @return a message with the correct class loader */ public static Message unpack(Object handler, Object message) { - if (handler instanceof FluxWrapper) { - handler = ((FluxWrapper) handler).getTarget(); - } if (!(handler instanceof Isolated)) { if (message instanceof Message) { return (Message) message; diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionRegistrationTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionRegistrationTests.java deleted file mode 100644 index 10f755f55..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionRegistrationTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.context; - -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class FunctionRegistrationTests { - - @Test - public void noTypeByDefault() { - FunctionRegistration registration = new FunctionRegistration<>(new Foos(), - "foos"); - assertThat(registration.getType()).isNull(); - assertThat(registration.getNames()).contains("foos"); - } - - @Test - public void wrap() { - FunctionRegistration registration = new FunctionRegistration<>(new Foos(), - "foos").type(FunctionType.of(Foos.class).getType()); - FunctionRegistration other = registration.wrap(); - assertThat(registration.getType().isWrapper()).isFalse(); - assertThat(other.getType().isWrapper()).isTrue(); - assertThat(other.getTarget()).isNotEqualTo(registration.getTarget()); - } - - private static class Foos implements Function { - - @Override - public String apply(Integer t) { - return "i=" + t; - } - - } - -} diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageConsumerTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageConsumerTests.java deleted file mode 100644 index 08221e709..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageConsumerTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.messaging.support.MessageBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - */ -public class MessageConsumerTests { - - private List items = new ArrayList<>(); - - @Test - public void plainConsumer() { - MessageConsumer consumer = new MessageConsumer(input()); - consumer.accept(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - assertThat(this.items).hasSize(1); - } - - private Consumer input() { - return value -> this.items.add(value); - } - -} diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageFunctionTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageFunctionTests.java deleted file mode 100644 index 54094ac64..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageFunctionTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; - -import org.springframework.cloud.function.core.FluxConsumer; -import org.springframework.cloud.function.core.FluxFunction; -import org.springframework.cloud.function.core.FluxToMonoFunction; -import org.springframework.cloud.function.core.MonoToFluxFunction; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - */ -public class MessageFunctionTests { - - private List items = new ArrayList<>(); - - @Test - public void plainFunction() { - MessageFunction function = new MessageFunction(uppercase()); - Publisher> result = function.apply(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - StepVerifier.create(result).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("FOO"); - assertThat(message.getHeaders()).containsEntry("foo", "bar"); - }); - } - - @Test - public void fluxFunction() { - MessageFunction function = new MessageFunction(new FluxFunction<>(uppercase())); - Publisher> result = function.apply(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - StepVerifier.create(result).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("FOO"); - assertThat(message.getHeaders()).containsEntry("foo", "bar"); - }); - } - - @Test - public void fluxToMonoFunction() { - MessageFunction function = new MessageFunction( - new FluxToMonoFunction( - flux -> flux.next().map(uppercase()))); - Publisher> result = function.apply(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - StepVerifier.create(result).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("FOO"); - assertThat(message.getHeaders()).containsEntry("foo", "bar"); - }); - } - - @Test - public void monoToFunction() { - MessageFunction function = new MessageFunction( - new MonoToFluxFunction( - mono -> Flux.from(mono.map(uppercase())))); - Publisher> result = function.apply(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - StepVerifier.create(result).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("FOO"); - assertThat(message.getHeaders()).containsEntry("foo", "bar"); - }); - } - - @Test - public void fluxConsumer() { - MessageFunction function = new MessageFunction(new FluxConsumer<>(stash())); - Publisher> result = function.apply(Flux - .just(MessageBuilder.withPayload("foo").setHeader("foo", "bar").build())); - StepVerifier.create(result).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo(null); - assertThat(message.getHeaders()).containsEntry("foo", "bar"); - assertThat(this.items).hasSize(1); - }); - } - - private Consumer stash() { - return value -> this.items.add(value); - } - - private Function uppercase() { - return value -> value.toUpperCase(); - } - -} diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageSupplierTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageSupplierTests.java deleted file mode 100644 index 8ff2bd6be..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/MessageSupplierTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.context.catalog; - -import java.util.Arrays; -import java.util.Collection; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - */ -public class MessageSupplierTests { - - @Test - public void plainSupplier() { - MessageSupplier supplier = new MessageSupplier(input()); - StepVerifier.create(supplier.get()).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("foo"); - assertThat(message.getHeaders()).isEmpty(); - }); - } - - @Test - public void collectionSupplier() { - MessageSupplier supplier = new MessageSupplier(inputs()); - StepVerifier.create(supplier.get()).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("foo"); - assertThat(message.getHeaders()).isEmpty(); - }).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("bar"); - assertThat(message.getHeaders()).isEmpty(); - }); - } - - @Test - public void fluxSupplier() { - MessageSupplier supplier = new MessageSupplier(flux()); - StepVerifier.create(supplier.get()).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("foo"); - assertThat(message.getHeaders()).isEmpty(); - }).assertNext(message -> { - assertThat(message.getPayload()).isEqualTo("bar"); - assertThat(message.getHeaders()).isEmpty(); - }); - } - - private Supplier input() { - return () -> "foo"; - } - - private Supplier> inputs() { - return () -> Arrays.asList("foo", "bar"); - } - - private Supplier> flux() { - return () -> Flux.just("foo", "bar"); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxConsumer.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxConsumer.java deleted file mode 100644 index 78e64f2e8..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxConsumer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.core; - -import java.util.function.Consumer; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Wrapper for a {@link Consumer} implementation that converts a non-reactive - * consumer into a reactive function ({@code Function, Mono>}). - * - * @param input type of target consumer - * @author Dave Syer - * @author Oleg Zhurakousky - * @see FluxedConsumer - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class FluxConsumer - extends WrappedFunction, Mono, Consumer> { - - public FluxConsumer(Consumer target) { - super(target); - } - - @Override - public Mono apply(Flux input) { - return input.doOnNext(this.getTarget()).then(); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxFunction.java deleted file mode 100644 index 74933963e..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxFunction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.core; - -import java.util.function.Function; - -import reactor.core.publisher.Flux; - -/** - * {@link Function} implementation that wraps a target Function so that the target's - * simple input and output types will be wrapped as {@link Flux} instances. - * - * @param input type of target function - * @param output type of target function - * @author Mark Fisher - * @author Oleg Zhurakousky - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class FluxFunction - extends WrappedFunction, Flux, Function> { - - public FluxFunction(Function target) { - super(target); - } - - @Override - public Flux apply(Flux input) { - return input.map(value -> this.getTarget().apply(value)); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxSupplier.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxSupplier.java deleted file mode 100644 index 8d28038a9..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxSupplier.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.core; - -import java.time.Duration; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import reactor.core.publisher.Flux; - -/** - * {@link Supplier} implementation that wraps a target Supplier so that the target's - * simple output type will be wrapped in a {@link Flux} instance. If a {@link Duration} is - * provided, the Flux will produce output periodically, invoking the target Supplier's - * {@code get} method at each interval. If no Duration is provided, the target will be - * invoked only once. - * - * @param output type of target supplier - * @author Mark Fisher - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class FluxSupplier implements Supplier>, FluxWrapper> { - - private final Supplier supplier; - - private final Duration period; - - public FluxSupplier(Supplier supplier) { - this(supplier, null); - } - - public FluxSupplier(Supplier supplier, Duration period) { - this.supplier = supplier; - this.period = period; - } - - @Override - public Supplier getTarget() { - return this.supplier; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Flux get() { - if (this.period != null) { - return Flux.interval(this.period).map(i -> this.supplier.get()); - } - Object result = this.supplier.get(); - if (result instanceof Stream) { - return Flux.fromStream((Stream) result); - } - return Flux.just((T) result); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxToMonoFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxToMonoFunction.java deleted file mode 100644 index a923d1823..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxToMonoFunction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.core; - -import java.util.function.Function; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Wrapper to mark function {@code Function, Mono>}. - * - * While it may look similar to {@link FluxedConsumer} the fundamental difference is that - * this class represents a function that returns {@link Mono} of type {@code }, while - * {@link FluxedConsumer} is a consumer that has been decorated as - * {@code Function, Mono>}. - * - * @param type of {@link Flux} input of the target function - * @param type of {@link Mono} output of the target function - * @author Oleg Zhurakousky - * @since 2.0 - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class FluxToMonoFunction - extends WrappedFunction, Mono, Function, Mono>> { - - public FluxToMonoFunction(Function, Mono> target) { - super(target); - } - - @Override - public Mono apply(Flux input) { - return this.getTarget().apply(input); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxWrapper.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxWrapper.java deleted file mode 100644 index 524e19a0c..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxWrapper.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.core; - -/** - * @param target type - * @author Dave Syer - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public interface FluxWrapper { - - T getTarget(); - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedConsumer.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedConsumer.java deleted file mode 100644 index 227b9e017..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedConsumer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.core; - -import java.util.function.Consumer; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Wrapper for a {@link Consumer} implementation that converts a reactive consumer into a - * reactive function ({@code Function, Mono>}). This is primarily done for - * consistent representation of reactive and non-reactive consumers. - * - * @param input type of target consumer - * @author Oleg Zhurakousky - * @since 2.0.1 - * @see FluxConsumer - * - * @deprecated since 3.1 no longer used by the framework - * - */ -@Deprecated -public class FluxedConsumer - extends WrappedFunction, Mono, Consumer>> { - - public FluxedConsumer(Consumer> target) { - super(target); - } - - @Override - public Mono apply(Flux input) { - return Mono.fromRunnable(() -> this.getTarget().accept(input)); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedFunction.java deleted file mode 100644 index 1808c5993..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FluxedFunction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.core; - -import java.util.function.Function; - -import reactor.core.publisher.Flux; - -/** - * {@link Function} implementation that wraps a target Function so that the target's - * simple input and output types will be wrapped as {@link Flux} instances. - * - * @param input type of target function - * @param output type of target function - * @author Oleg Zhurakousky - * @since 2.0.1 - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class FluxedFunction - extends WrappedFunction, Flux, Function, Flux>> { - - public FluxedFunction(Function, Flux> target) { - super(target); - } - - @Override - public Flux apply(Flux input) { - return input.transform(this.getTarget()); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FunctionFactoryMetadata.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FunctionFactoryMetadata.java deleted file mode 100644 index 4f5f0b10d..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/FunctionFactoryMetadata.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.core; - -import java.lang.reflect.Method; - -/** - * @param target type - * @author Dave Syer - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public interface FunctionFactoryMetadata { - - Method getFactoryMethod(); - - F getTarget(); - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedSupplier.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedSupplier.java deleted file mode 100644 index 7c9c8ba07..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedSupplier.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.core; - -import java.util.function.Supplier; - -import org.springframework.util.ClassUtils; - -/** - * @param supplied type - * @author Dave Syer - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class IsolatedSupplier implements Supplier, Isolated { - - private final Supplier supplier; - - private final ClassLoader classLoader; - - public IsolatedSupplier(Supplier supplier) { - this.supplier = supplier; - this.classLoader = supplier.getClass().getClassLoader(); - } - - @Override - public ClassLoader getClassLoader() { - return this.classLoader; - } - - @Override - public T get() { - ClassLoader context = ClassUtils - .overrideThreadContextClassLoader(this.classLoader); - try { - return this.supplier.get(); - } - finally { - ClassUtils.overrideThreadContextClassLoader(context); - } - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoSupplier.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoSupplier.java deleted file mode 100644 index d1a5d9222..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoSupplier.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.core; - -import java.util.function.Supplier; - -import reactor.core.publisher.Mono; - -/** - * {@link Supplier} implementation that wraps a target Supplier so that the target's - * simple output type will be wrapped in a {@link Mono} instance. - * - * @param output type of target supplier - * @author Mark Fisher - * @since 2.1 - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class MonoSupplier implements Supplier>, FluxWrapper> { - - private final Supplier supplier; - - public MonoSupplier(Supplier supplier) { - this.supplier = supplier; - } - - @Override - public Supplier getTarget() { - return this.supplier; - } - - @Override - @SuppressWarnings("unchecked") - public Mono get() { - Object result = this.supplier.get(); - return Mono.just((T) result); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoToFluxFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoToFluxFunction.java deleted file mode 100644 index e0cf7f2c4..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/MonoToFluxFunction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.core; - -import java.util.function.Function; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Marker wrapper for target Function<Mono, Flux>. - * - * @param type of {@link Mono} input of the target function - * @param type of {@link Flux} output of the target function - * @author Oleg Zhurakousky - * @since 2.0 - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public class MonoToFluxFunction - extends WrappedFunction, Flux, Function, Flux>> { - - public MonoToFluxFunction(Function, Flux> target) { - super(target); - } - - @Override - public Flux apply(Mono input) { - return this.getTarget().apply(input); - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/WrappedFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/WrappedFunction.java deleted file mode 100644 index 65b762b7d..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/WrappedFunction.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.core; - -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.reactivestreams.Publisher; - -/** - * Base class for all wrappers that represent underlying functions (user defined - * suppliers, functions and/or consumers) as reactive functions. - * - * @param input type of target consumer - * @param output type of target consumer - * @param reactive input type of target function (instance of {@link Publisher} - * @param reactive output type of target function (instance of {@link Publisher} - * @param actual target function (instance of {@link Supplier}, {@link Function} or - * {@link Consumer}) - * @author Oleg Zhurakousky - * @since 2.0.1 - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public abstract class WrappedFunction, OP extends Publisher, T> - implements Function, FluxWrapper { - - private final T target; - - WrappedFunction(T target) { - this.target = target; - } - - @Override - public T getTarget() { - return this.target; - } - -} From 3eea7082f6ed2537b83c64aba50f557748f6afc4 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 8 Nov 2021 17:02:40 +0100 Subject: [PATCH 03/41] Remove RequestProcessor from web --- .../cloud/function/web/RequestProcessor.java | 352 ------------------ .../function/FunctionEndpointInitializer.java | 28 +- .../FunctionWebRequestProcessingHelper.java | 4 + 3 files changed, 15 insertions(+), 369 deletions(-) delete mode 100644 spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java 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 deleted file mode 100644 index 138a8b617..000000000 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/RequestProcessor.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright 2017-2020 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.web; - -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.cloud.function.context.message.MessageUtils; -import org.springframework.cloud.function.json.JsonMapper; -import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper; -import org.springframework.cloud.function.web.util.HeaderUtils; -import org.springframework.core.ResolvableType; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.ResponseEntity.BodyBuilder; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.messaging.Message; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - */ -public class RequestProcessor { - - private static Log logger = LogFactory.getLog(RequestProcessor.class); - - private final JsonMapper mapper; - - public RequestProcessor(JsonMapper mapper, - ObjectProvider codecs) { - this.mapper = mapper; - } - - public static FunctionWrapper wrapper(FunctionInvocationWrapper function) { - return new FunctionWrapper(function); - } - - @SuppressWarnings("rawtypes") - public Mono> get(FunctionWrapper wrapper) { - if (wrapper.function().isFunction()) { - return response(wrapper, wrapper.function(), invokeFunction(wrapper), true, true); - } - else { - FunctionInvocationWrapper function = (wrapper.function); - Object result = FunctionWebRequestProcessingHelper.invokeFunction(function, null, false); - return response(wrapper, wrapper.function(), result instanceof Publisher ? (Publisher) result : Flux.just(result), null, - true); - } - - } - - public Mono> post(FunctionWrapper wrapper, String body, - boolean stream) { - FunctionInvocationWrapper function = (FunctionInvocationWrapper) wrapper.handler(); - Type itemType = function != null ? function.getItemType(function.getInputType()) : Object.class; - - Object input = body == null ? "" : body; - - /* - * We need this to ensure that imperative function which are sent array-like input - * can be invoked with each item and then aggregated - */ - if (input != null && JsonMapper.isJsonStringRepresentsCollection(input)) { - Type type = FunctionTypeUtils.isTypeCollection(itemType) - ? ResolvableType.forType(itemType).getType() - : ResolvableType.forClassWithGenerics(Collection.class, ResolvableType.forType(itemType)).asCollection().getType(); - input = this.mapper.fromJson((String) input, type); - } - - return response(wrapper, input, stream); - } - - public Mono> stream(FunctionWrapper functionWrapper) { - Publisher result = functionWrapper.function.isFunction() - ? invokeFunction(functionWrapper) - : (Publisher) functionWrapper.function.get(); - return stream(functionWrapper, result); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Mono> response(FunctionWrapper wrapper, Object body, boolean stream) { - - FunctionInvocationWrapper function = (wrapper.function()); - Flux flux; - Class inputType = function == null - ? Object.class - : FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(function.getInputType())); - if (MultiValueMap.class.isAssignableFrom(inputType)) { - body = null; - flux = Flux.just(wrapper.params()); - } - else if (body != null) { - if (Collection.class.isAssignableFrom(inputType)) { - flux = Flux.just(body); - } - else if (body instanceof Flux) { - flux = Flux.from((Flux) body); - } - else { - Iterable iterable = body instanceof Collection - ? (Collection) body - : Collections.singletonList(body); - flux = Flux.fromIterable(iterable); - } - } - else { - throw new IllegalStateException( - "Failed to determine input for function call with parameters: '" - + wrapper.params + "' and headers: `" + wrapper.headers - + "`"); - } - - if (function != null) { - flux = messages(wrapper, function, flux); - } - Mono> responseEntityMono = null; - - if (function == null) { - responseEntityMono = Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND) - .body("Function for provided path can not be found")); - } - else { - Publisher result = (Publisher) FunctionWebRequestProcessingHelper.invokeFunction(function, flux, function.isInputTypeMessage()); - if (function.isConsumer()) { - if (result != null) { - ((Mono) result).subscribe(); - } - logger.debug("Handled POST with consumer"); - responseEntityMono = Mono.just(ResponseEntity.status(HttpStatus.ACCEPTED).build()); - } - else { - result = Flux.from((Publisher) result); - logger.debug("Handled POST with function: " + function); - if (stream) { - responseEntityMono = stream(wrapper, result); - } - else { - responseEntityMono = response(wrapper, function, result, - body == null ? null : !(body instanceof Collection), false); - } - } - } - return responseEntityMono; - } - - private Mono> response(FunctionWrapper request, Object handler, - Publisher result, Boolean single, boolean getter) { - BodyBuilder builder = ResponseEntity.ok(); - if (result instanceof Mono) { - result = Mono.from(result) - .map(message -> MessageUtils.unpack(handler, message)) - .doOnNext(value -> { - addHeaders(builder, value); - if (!isValidCloudEvent(value.getHeaders().keySet())) { -// builder.headers(HeaderUtils.sanitize(request.headers())); - } - }) - .map(message -> message.getPayload()); - } - else { - result = Flux.from(result) - .map(message -> MessageUtils.unpack(handler, message)) - .doOnNext(value -> { - addHeaders(builder, value); - if (!isValidCloudEvent(value.getHeaders().keySet())) { -// builder.headers(HeaderUtils.sanitize(request.headers())); - } - }) - .map(message -> message.getPayload()); - } - - if (isOutputSingle(handler) - && (single != null && single || getter || isInputMultiple(handler))) { - result = Mono.from(result); - } - - if (result instanceof Flux) { - result = Flux.from(result).onErrorContinue((e, v) -> { - logger.error("Failed to process value: " + v, e); - }) - .collectList(); - } - return Mono.from(result).flatMap(body -> Mono.just(builder.body(body))); - } - - private boolean isValidCloudEvent(Set headerKeys) { - return headerKeys.contains("ce-id") - && headerKeys.contains("ce-source") - && headerKeys.contains("ce-type") - && headerKeys.contains("ce-specversion"); - } - - // this seem to be very relevant to AWS container tests - private Flux messages(FunctionWrapper request, Object function, Flux flux) { - Map headers = new HashMap<>(HeaderUtils.fromHttp(request.headers())); - if (function instanceof FunctionInvocationWrapper) { - headers.put("scf-func-name", ((FunctionInvocationWrapper) function).getFunctionDefinition()); - } - return flux.map(payload -> MessageUtils.create(function, payload, headers)); - } - - private void addHeaders(BodyBuilder builder, Message message) { - builder.headers(HeaderUtils.fromMessage(message.getHeaders())); - } - - private Mono> stream(FunctionWrapper request, Publisher result) { - BodyBuilder builder = ResponseEntity.ok(); - if (((FunctionInvocationWrapper) request.handler()).isInputTypeMessage()) { - result = Flux.from(result) - .doOnNext(value -> addHeaders(builder, (Message) value)) - .map(message -> MessageUtils.unpack(request.handler(), message) - .getPayload()); - } - else { - builder.headers(HeaderUtils.sanitize(request.headers())); - } - - Publisher output = result; - return Flux.from(output).then(Mono.fromSupplier(() -> builder.body(output))); - } - - - - private boolean isInputMultiple(Object handler) { - FunctionInvocationWrapper function = (FunctionInvocationWrapper) handler; - Class type = function == null ? Object.class : FunctionTypeUtils - .getRawType(FunctionTypeUtils.getGenericType(function.getInputType())); - return Collection.class.isAssignableFrom(type) || (function != null && FunctionTypeUtils.isFlux(function.getInputType())); - - } - - private boolean isOutputSingle(Object handler) { - FunctionInvocationWrapper function = (FunctionInvocationWrapper) handler; - Type outputType = function.getOutputType(); - Class type = FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(outputType)); - Class wrapper = function.isOutputTypePublisher() ? FunctionTypeUtils.getRawType(outputType) : type; - if (Stream.class.isAssignableFrom(type)) { - return false; - } - else { - return wrapper == type || Mono.class.equals(wrapper) - || Optional.class.equals(wrapper); - } - } - - private Publisher invokeFunction(FunctionWrapper wrapper) { - if (wrapper.argument != null) { - Flux input = Flux.from(wrapper.argument); - Object result = FunctionWebRequestProcessingHelper.invokeFunction(wrapper.function, input, wrapper.function.isInputTypeMessage()); - return Mono.from((Publisher) result); - } - else { - return Mono.empty(); - } - } - - /** - * Wrapper for functions. - */ - public static class FunctionWrapper { - - private final FunctionInvocationWrapper function; - - private final MultiValueMap params = new LinkedMultiValueMap<>(); - - private HttpHeaders headers = new HttpHeaders(); - - private Publisher argument; - - public FunctionWrapper(FunctionInvocationWrapper function) { - this.function = function; - } - - public Object handler() { - return this.function; - } - - public FunctionInvocationWrapper function() { - return this.function; - } - - @Deprecated - public Supplier supplier() { - return this.function; - } - - public MultiValueMap params() { - return this.params; - } - - public HttpHeaders headers() { - return this.headers; - } - - public FunctionWrapper headers(HttpHeaders headers) { - this.headers = headers; - return this; - } - - public FunctionWrapper params(MultiValueMap params) { - this.params.addAll(params); - return this; - } - - public FunctionWrapper argument(Publisher argument) { - this.argument = argument; - return this; - } - - public FunctionWrapper argument(String argument) { - this.argument = Mono.just(argument); - return this; - } - - public Publisher argument() { - return this.argument; - } - } -} diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java index 9075a0a42..b15deecca 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializer.java @@ -41,11 +41,9 @@ import org.springframework.cloud.function.context.FunctionalSpringApplication; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer; -import org.springframework.cloud.function.json.JsonMapper; -import org.springframework.cloud.function.web.RequestProcessor; -import org.springframework.cloud.function.web.RequestProcessor.FunctionWrapper; import org.springframework.cloud.function.web.constants.WebRequestConstants; import org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper; +import org.springframework.cloud.function.web.util.FunctionWrapper; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEvent; @@ -103,12 +101,9 @@ public class FunctionEndpointInitializer implements ApplicationContextInitialize } private void registerEndpoint(GenericApplicationContext context) { - context.registerBean(RequestProcessor.class, - () -> new RequestProcessor(context.getBeansOfType(JsonMapper.class).values().iterator().next(), - context.getBeanProvider(ServerCodecConfigurer.class))); context.registerBean(FunctionEndpointFactory.class, () -> new FunctionEndpointFactory(context.getBean(FunctionProperties.class), context.getBean(FunctionCatalog.class), - context.getBean(RequestProcessor.class), context.getEnvironment())); + context.getEnvironment())); RouterFunctionRegister.register(context); } @@ -203,16 +198,13 @@ class FunctionEndpointFactory { private final String handler; - private final RequestProcessor processor; - private final FunctionProperties functionProperties; - FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, RequestProcessor processor, Environment environment) { + FunctionEndpointFactory(FunctionProperties functionProperties, FunctionCatalog functionCatalog, Environment environment) { String handler = environment.resolvePlaceholders("${function.handler}"); if (handler.startsWith("$")) { handler = null; } - this.processor = processor; this.functionCatalog = functionCatalog; this.handler = handler; this.functionProperties = functionProperties; @@ -240,9 +232,10 @@ class FunctionEndpointFactory { FunctionInvocationWrapper funcWrapper = extract(request); Class outputType = funcWrapper == null ? Object.class : FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(funcWrapper.getOutputType())); - FunctionWrapper wrapper = RequestProcessor.wrapper(funcWrapper); + FunctionWrapper wrapper = new FunctionWrapper(funcWrapper); Mono> stream = request.bodyToMono(String.class) - .flatMap(content -> this.processor.post(wrapper, content, false)); + .flatMap(content -> (Mono>) FunctionWebRequestProcessingHelper.processRequest(wrapper, content, false)); + return stream.flatMap(entity -> { return status(entity.getStatusCode()).headers(headers -> headers.addAll(entity.getHeaders())) .body(entity.hasBody() ? Mono.just((T) entity.getBody()) : Mono.empty(), outputType); @@ -259,11 +252,12 @@ class FunctionEndpointFactory { return ServerResponse.ok().body(result, outputType); } else { - FunctionWrapper wrapper = RequestProcessor.wrapper(funcWrapper); - wrapper.headers(request.headers().asHttpHeaders()); + FunctionWrapper wrapper = new FunctionWrapper(funcWrapper); + + wrapper.setHeaders(request.headers().asHttpHeaders()); String argument = (String) request.attribute(WebRequestConstants.ARGUMENT).get(); - wrapper.argument(Flux.just(argument)); - Object result = FunctionWebRequestProcessingHelper.invokeFunction(funcWrapper, wrapper.argument(), + wrapper.setArgument(Flux.just(argument)); + Object result = FunctionWebRequestProcessingHelper.invokeFunction(funcWrapper, wrapper.getArgument(), funcWrapper.isInputTypeMessage()); return ServerResponse.ok().body(result, outputType); } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java index b459a6954..b78037908 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/util/FunctionWebRequestProcessingHelper.java @@ -87,6 +87,10 @@ public final class FunctionWebRequestProcessingHelper { public static Object processRequest(FunctionWrapper wrapper, Object argument, boolean eventStream) { FunctionInvocationWrapper function = wrapper.getFunction(); + if (function == null) { + return Mono.just(ResponseEntity.notFound().build()); + } + HttpHeaders headers = wrapper.getHeaders(); Message inputMessage = argument == null ? null : MessageBuilder.withPayload(argument).copyHeaders(headers.toSingleValueMap()).build(); From 0e2334b1549f72b1ee0bf17aab4c210d58664dbb Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 8 Nov 2021 17:20:49 +0100 Subject: [PATCH 04/41] Additional removals of deprecated classes from core --- .../context/message/MessageUtils.java | 81 ------------------- .../cloud/function/core/Isolated.java | 27 ------- .../cloud/function/core/IsolatedConsumer.java | 55 ------------- .../cloud/function/core/IsolatedFunction.java | 56 ------------- 4 files changed, 219 deletions(-) delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/Isolated.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedConsumer.java delete mode 100644 spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedFunction.java diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java index 6b7aa1140..585793feb 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/message/MessageUtils.java @@ -16,17 +16,6 @@ package org.springframework.cloud.function.context.message; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.cloud.function.core.Isolated; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - /** * @author Dave Syer * @author Oleg Zhurakousky @@ -47,74 +36,4 @@ public abstract class MessageUtils { * Value for 'target-protocol' typically use as header key. */ public static String SOURCE_TYPE = "source-type"; - - /** - * Create a message for the handler. If the handler is a wrapper for a function in an - * isolated class loader, then the message will be created with the target class - * loader (therefore the {@link Message} class must be on the classpath of the target - * class loader). - * @param handler the function that will be applied to the message - * @param payload the payload of the message - * @param headers the headers for the message - * @return a message with the correct class loader - */ - public static Object create(Object handler, Object payload, - Map headers) { - if (payload instanceof Message) { - headers = new HashMap<>(headers); - headers.putAll(((Message) payload).getHeaders()); - payload = ((Message) payload).getPayload(); - } - if (!(handler instanceof Isolated)) { - return MessageBuilder.withPayload(payload).copyHeaders(headers).build(); - } - ClassLoader classLoader = ((Isolated) handler).getClassLoader(); - Class builder = ClassUtils.resolveClassName(MessageBuilder.class.getName(), - classLoader); - Method withPayload = ClassUtils.getMethod(builder, "withPayload", Object.class); - Method copyHeaders = ClassUtils.getMethod(builder, "copyHeaders", Map.class); - Method build = ClassUtils.getMethod(builder, "build"); - Object instance = ReflectionUtils.invokeMethod(withPayload, null, payload); - ReflectionUtils.invokeMethod(copyHeaders, instance, headers); - return ReflectionUtils.invokeMethod(build, instance); - } - - /** - * Convert a message from the handler into one that is safe to consume in the caller's - * class loader. If the handler is a wrapper for a function in an isolated class - * loader, then the message will be created with the target class loader (therefore - * the {@link Message} class must be on the classpath of the target class loader). - * @param handler the function that generated the message - * @param message the message to convert - * @return a message with the correct class loader - */ - public static Message unpack(Object handler, Object message) { - if (!(handler instanceof Isolated)) { - if (message instanceof Message) { - return (Message) message; - } - return MessageBuilder.withPayload(message).build(); - } - ClassLoader classLoader = ((Isolated) handler).getClassLoader(); - Class type = ClassUtils.isPresent(Message.class.getName(), classLoader) - ? ClassUtils.resolveClassName(Message.class.getName(), classLoader) - : null; - Object payload; - Map headers; - if (type != null && type.isAssignableFrom(message.getClass())) { - Method getPayload = ClassUtils.getMethod(type, "getPayload"); - Method getHeaders = ClassUtils.getMethod(type, "getHeaders"); - payload = ReflectionUtils.invokeMethod(getPayload, message); - @SuppressWarnings("unchecked") - Map map = (Map) ReflectionUtils - .invokeMethod(getHeaders, message); - headers = map; - } - else { - payload = message; - headers = Collections.emptyMap(); - } - return MessageBuilder.withPayload(payload).copyHeaders(headers).build(); - } - } diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/Isolated.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/Isolated.java deleted file mode 100644 index 3b7d7779a..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/Isolated.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.core; - -/** - * @author Dave Syer - * - */ -public interface Isolated { - - ClassLoader getClassLoader(); - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedConsumer.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedConsumer.java deleted file mode 100644 index 1eefb1bc0..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedConsumer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.core; - -import java.util.function.Consumer; - -import org.springframework.util.ClassUtils; - -/** - * @param type to consume - * @author Dave Syer - */ -public class IsolatedConsumer implements Consumer, Isolated { - - private final Consumer consumer; - - private final ClassLoader classLoader; - - public IsolatedConsumer(Consumer consumer) { - this.consumer = consumer; - this.classLoader = consumer.getClass().getClassLoader(); - } - - @Override - public ClassLoader getClassLoader() { - return this.classLoader; - } - - @Override - public void accept(T item) { - ClassLoader context = ClassUtils - .overrideThreadContextClassLoader(this.classLoader); - try { - this.consumer.accept(item); - } - finally { - ClassUtils.overrideThreadContextClassLoader(context); - } - } - -} diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedFunction.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedFunction.java deleted file mode 100644 index c75122327..000000000 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/core/IsolatedFunction.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.core; - -import java.util.function.Function; - -import org.springframework.util.ClassUtils; - -/** - * @param input type - * @param output type - * @author Dave Syer - */ -public class IsolatedFunction implements Function, Isolated { - - private final Function function; - - private final ClassLoader classLoader; - - public IsolatedFunction(Function function) { - this.function = function; - this.classLoader = function.getClass().getClassLoader(); - } - - @Override - public ClassLoader getClassLoader() { - return this.classLoader; - } - - @Override - public T apply(S item) { - ClassLoader context = ClassUtils - .overrideThreadContextClassLoader(this.classLoader); - try { - return this.function.apply(item); - } - finally { - ClassUtils.overrideThreadContextClassLoader(context); - } - } - -} From afb06fcc2b7e09bab7114f05ac478f2d7a71c9f0 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 8 Nov 2021 18:27:14 +0100 Subject: [PATCH 05/41] Remove deprecated AWS classes --- .../aws/CustomRuntimeAutoConfiguration.java | 2 + ...CustomRuntimeEnvironmentPostProcessor.java | 59 ---- .../SpringBootApiGatewayRequestHandler.java | 149 ---------- .../aws/SpringBootKinesisEventHandler.java | 97 ------- .../adapter/aws/SpringBootRequestHandler.java | 106 ------- .../adapter/aws/SpringBootStreamHandler.java | 92 ------ ...ringBootApiGatewayRequestHandlerTests.java | 273 ------------------ .../SpringBootKinesisEventHandlerTests.java | 231 --------------- .../aws/SpringBootRequestHandlerTests.java | 103 ------- .../aws/SpringBootStreamHandlerTests.java | 229 --------------- .../cloud/function/grpc/GrpcUtils.java | 2 +- 11 files changed, 3 insertions(+), 1340 deletions(-) delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEnvironmentPostProcessor.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandler.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandler.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandler.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandlerTests.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandlerTests.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandlerTests.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandlerTests.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeAutoConfiguration.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeAutoConfiguration.java index c9730be45..0c2ffea2f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeAutoConfiguration.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeAutoConfiguration.java @@ -29,6 +29,8 @@ import org.springframework.context.annotation.Configuration; /** * @author Dave Syer */ +//TODO - do we actually need it????? + @Configuration @AutoConfigureBefore(FunctionExporterAutoConfiguration.class) @ConditionalOnClass(DestinationResolver.class) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEnvironmentPostProcessor.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEnvironmentPostProcessor.java deleted file mode 100644 index 5ed4e51fe..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEnvironmentPostProcessor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019-2021 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.aws; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; - -/** - * Adds default properties to the environment for running a custom runtime in AWS. - * - * @author Dave Syer - * @author Oleg Zhurakousky - */ -public class CustomRuntimeEnvironmentPostProcessor implements EnvironmentPostProcessor { - - private static final String CUSTOM_RUNTIME = "spring.cloud.function.aws.custom"; - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, - SpringApplication application) { - if (!environment.containsProperty(CUSTOM_RUNTIME)) { - Map defaults = getDefaultProperties(environment); - defaults.put(CUSTOM_RUNTIME, true); - } - } - - private Map getDefaultProperties( - ConfigurableEnvironment environment) { - if (environment.getPropertySources().contains("defaultProperties")) { - MapPropertySource source = (MapPropertySource) environment - .getPropertySources().get("defaultProperties"); - return source.getSource(); - } - HashMap map = new HashMap(); - environment.getPropertySources() - .addLast(new MapPropertySource("defaultProperties", map)); - return map; - } - -} 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 deleted file mode 100644 index 1968738ab..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandler.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2012-2020 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.aws; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import com.amazonaws.services.lambda.runtime.Context; -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.apache.commons.codec.binary.Base64; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.http.HttpStatus; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.GenericMessage; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Semyon Fishman - * @author Markus Gulden - * - * @deprecated since 3.1 in favor of {@link FunctionInvoker} - */ -@Deprecated -public class SpringBootApiGatewayRequestHandler extends - SpringBootRequestHandler { - - @Autowired - private ObjectMapper mapper; - - public SpringBootApiGatewayRequestHandler(Class configurationClass) { - super(configurationClass); - } - - public SpringBootApiGatewayRequestHandler() { - super(); - } - - @Override - protected Object convertEvent(APIGatewayProxyRequestEvent event) { - Object deserializedBody = event.getBody() != null ? deserializeBody(event) : Optional.empty(); - return functionAcceptsMessage() - ? new GenericMessage<>(deserializedBody, getHeaders(event)) - : deserializedBody; - } - - private boolean functionAcceptsMessage() { - return ((FunctionInvocationWrapper) function()).isInputTypeMessage(); - } - - private Object deserializeBody(APIGatewayProxyRequestEvent event) { - try { - return this.mapper.readValue( - (event.getIsBase64Encoded() != null && event.getIsBase64Encoded()) - ? new String(Base64.decodeBase64(event.getBody())) : event.getBody(), - 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()); - } - if (event.getQueryStringParameters() != null) { - headers.putAll(event.getQueryStringParameters()); - } - if (event.getPathParameters() != null) { - headers.putAll(event.getPathParameters()); - } - headers.put("httpMethod", event.getHttpMethod()); - headers.put("request", event); - return new MessageHeaders(headers); - } - - @Override - protected APIGatewayProxyResponseEvent convertOutput(Object output) { - if (functionReturnsMessage(output)) { - Message message = (Message) output; - return new APIGatewayProxyResponseEvent() - .withStatusCode((Integer) message.getHeaders() - .getOrDefault("statuscode", HttpStatus.OK.value())) - .withHeaders(toResponseHeaders(message.getHeaders())) - .withBody(serializeBody(message.getPayload())); - } - else { - return new APIGatewayProxyResponseEvent() - .withStatusCode(HttpStatus.OK.value()) - .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 this.mapper.writeValueAsString(body); - } - catch (JsonProcessingException e) { - throw new IllegalStateException("Cannot convert output", e); - } - } - - @Override - public Object handleRequest(APIGatewayProxyRequestEvent event, Context context) { - Object response = super.handleRequest(event, context); - if (returnsOutput()) { - return response; - } - else { - return new APIGatewayProxyResponseEvent() - .withStatusCode(HttpStatus.OK.value()); - } - } -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandler.java deleted file mode 100644 index 506d7e03b..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandler.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2012-2020 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.aws; - -import java.util.List; -import java.util.stream.Collectors; - -import com.amazonaws.kinesis.deagg.RecordDeaggregator; -import com.amazonaws.services.kinesis.clientlibrary.types.UserRecord; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.KinesisEvent; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; - -import static java.util.stream.Collectors.toList; - - -/** - * @param payload type - * @param response type - * @author Mark Fisher - * @author Halvdan Hoem Grelland - * @author Oleg Zhurakousky - * - * @deprecated since 3.1 in favor of {@link FunctionInvoker} - */ -@Deprecated -public class SpringBootKinesisEventHandler - extends SpringBootRequestHandler { - - @Autowired - private ObjectMapper mapper; - - public SpringBootKinesisEventHandler() { - super(); - } - - public SpringBootKinesisEventHandler(Class configurationClass) { - super(configurationClass); - } - - @SuppressWarnings("unchecked") - @Override - public List handleRequest(KinesisEvent event, Context context) { - return (List) super.handleRequest(event, context); - } - - @Override - protected Object convertEvent(KinesisEvent event) { - List payloads = deserializePayloads(event.getRecords()); - - if (((FunctionInvocationWrapper) function()).isInputTypeMessage()) { - return wrapInMessages(payloads); - } - else { - return payloads; - } - } - - private List> wrapInMessages(List payloads) { - return payloads.stream().map(GenericMessage::new).collect(Collectors.toList()); - } - - private List deserializePayloads(List records) { - return RecordDeaggregator.deaggregate(records).stream() - .map(this::deserializeUserRecord).collect(toList()); - } - - @SuppressWarnings("unchecked") - private E deserializeUserRecord(UserRecord userRecord) { - try { - byte[] jsonBytes = userRecord.getData().array(); - return (E) this.mapper.readValue(jsonBytes, getInputType()); - } - catch (Exception e) { - throw new IllegalStateException("Cannot convert event", 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 deleted file mode 100644 index 95bb543ec..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandler.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.aws; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.messaging.Message; - -/** - * @param event type - * @param result types - * @author Mark Fisher - * @author Oleg Zhurakousky - * - */ -@Deprecated -public class SpringBootRequestHandler extends AbstractSpringFunctionAdapterInitializer - implements RequestHandler { - - public SpringBootRequestHandler(Class configurationClass) { - super(configurationClass); - } - - public SpringBootRequestHandler() { - super(); - } - - @Override - public Object handleRequest(E event, Context context) { - initialize(context); - Object input = acceptsInput() ? convertEvent(event) : ""; - Publisher output = apply(extract(input)); - return result(input, output); - } - - @SuppressWarnings("unchecked") - @Override - protected T result(Object input, Publisher output) { - List result = new ArrayList<>(); - for (Object value : Flux.from(output).toIterable()) { - if (value instanceof Message && !((FunctionInvocationWrapper) this.function()).isOutputTypeMessage()) { - value = ((Message) value).getPayload(); - } - result.add(convertOutput(value)); - } - if (isSingleValue(input) && result.size() == 1) { - return (T) result.get(0); - } - return (T) result; - } - - protected boolean acceptsInput() { - Type inputType = ((FunctionInvocationWrapper) this.function()).getInputType(); - return inputType == null || inputType.equals(Void.class) ? false : true; - } - - protected boolean returnsOutput() { - Type outputType = ((FunctionInvocationWrapper) this.function()).getOutputType(); - return outputType == null || outputType.equals(Void.class) ? false : true; - } - - 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 Object convertEvent(E event) { - return event; - } - - @SuppressWarnings("unchecked") - 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/SpringBootStreamHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandler.java deleted file mode 100644 index 186f629c3..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2017-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.aws; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collection; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - */ -public class SpringBootStreamHandler extends AbstractSpringFunctionAdapterInitializer - implements RequestStreamHandler { - - @Autowired(required = false) - private ObjectMapper mapper; - - public SpringBootStreamHandler() { - super(); - } - - public SpringBootStreamHandler(Class configurationClass) { - super(configurationClass); - } - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - initialize(context); - Object value = convertStream(input); - Publisher flux = apply(extract(value)); - this.mapper.writeValue(output, result(value, flux)); - } - - @Override - protected void initialize(Context context) { - super.initialize(context); - if (this.mapper == null) { - this.mapper = new ObjectMapper(); - } - } - - private Flux extract(Object input) { - if (input instanceof Collection) { - return Flux.fromIterable((Iterable) input); - } - return Flux.just(input); - } - - /* - * Will convert to POJOP or generic map unless user - * explicitly requests InputStream (e.g., Function). - */ - private Object convertStream(InputStream input) { - Object convertedResult = input; - try { - Class inputType = getInputType(); - if (!InputStream.class.isAssignableFrom(inputType)) { - convertedResult = this.mapper.readValue(input, inputType); - } - } - catch (Exception e) { - throw new IllegalStateException("Cannot convert event stream", e); - } - return convertedResult; - } - -} 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 deleted file mode 100644 index 425e040cd..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootApiGatewayRequestHandlerTests.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.aws; - -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.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 static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dimitry Declercq - * @author Markus Gulden - */ -public class SpringBootApiGatewayRequestHandlerTests { - - private SpringBootApiGatewayRequestHandler handler; - - @AfterEach - public void after() { - System.clearProperty("function.name"); - } - - @Test - public void supplierBean() { - System.setProperty("function.name", "supplier"); - this.handler = new SpringBootApiGatewayRequestHandler(FunctionConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - - Object output = this.handler.handleRequest(request, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - assertThat(((APIGatewayProxyResponseEvent) output).getBody()) - .isEqualTo("\"hello!\""); - } - - @Test - public void functionBean() { - System.setProperty("function.name", "function"); - this.handler = new SpringBootApiGatewayRequestHandler(FunctionConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setBody("{\"value\":\"foo\"}"); - - Object output = this.handler.handleRequest(request, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - assertThat(((APIGatewayProxyResponseEvent) output).getBody()) - .isEqualTo("{\"value\":\"FOO\"}"); - - APIGatewayProxyRequestEvent bodyEncryptedRequest = new APIGatewayProxyRequestEvent(); - bodyEncryptedRequest.setBody( - Base64.getEncoder().encodeToString("{\"value\":\"foo\"}".getBytes())); - bodyEncryptedRequest.setIsBase64Encoded(true); - - output = this.handler.handleRequest(bodyEncryptedRequest, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - assertThat(((APIGatewayProxyResponseEvent) output).getBody()) - .isEqualTo("{\"value\":\"FOO\"}"); - } - - @Test - public void consumerBean() { - System.setProperty("function.name", "consumer"); - this.handler = new SpringBootApiGatewayRequestHandler(FunctionConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setBody("\"strVal\":\"test for consumer\""); - - Object output = this.handler.handleRequest(request, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - } - - @Test - public void functionMessageBean() { - this.handler = new SpringBootApiGatewayRequestHandler( - FunctionMessageConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setBody("{\"value\":\"foo\"}"); - - Object output = this.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\"}"); - } - - - @Test - public void functionMessageBeanWithRequestParameters() { - this.handler = new SpringBootApiGatewayRequestHandler( - FunctionMessageEchoReqParametersConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setPathParameters(Collections.singletonMap("path", "pathValue")); - request.setQueryStringParameters(Collections.singletonMap("query", "queryValue")); - request.setHeaders(Collections.singletonMap("test-header", "headerValue")); - request.setHttpMethod("GET"); - - Object output = this.handler.handleRequest(request, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - assertThat(((APIGatewayProxyResponseEvent) output).getHeaders().get("path")) - .isEqualTo("pathValue"); - assertThat(((APIGatewayProxyResponseEvent) output).getHeaders().get("query")) - .isEqualTo("queryValue"); - assertThat( - ((APIGatewayProxyResponseEvent) output).getHeaders().get("test-header")) - .isEqualTo("headerValue"); - assertThat(((APIGatewayProxyResponseEvent) output).getHeaders().get("httpMethod")) - .isEqualTo("GET"); - assertThat(((APIGatewayProxyResponseEvent) output).getBody()) - .isEqualTo("{\"value\":\"body\"}"); - - } - - @Test - public void functionMessageBeanWithEmptyResponse() { - this.handler = new SpringBootApiGatewayRequestHandler( - FunctionMessageConsumerConfig.class); - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - - Object output = this.handler.handleRequest(request, null); - assertThat(output).isInstanceOf(APIGatewayProxyResponseEvent.class); - assertThat(((APIGatewayProxyResponseEvent) output).getStatusCode()) - .isEqualTo(200); - assertThat(((APIGatewayProxyResponseEvent) output).getBody()).isNull(); - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionConfig { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - @Bean - public Consumer consumer() { - return v -> System.out.println(v); - } - - @Bean - public Supplier supplier() { - return () -> "hello!"; - } - - } - - @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); - }); - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionMessageEchoReqParametersConfig { - - @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, - JacksonAutoConfiguration.class }) - protected static class FunctionMessageConsumerConfig { - - @Bean - public Consumer> function() { - return (foo -> { - }); - } - - } - - protected static class Foo { - - private String value; - - public String getValue() { - return this.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 this.value; - } - - public void setValue(String value) { - this.value = value; - } - - } - -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandlerTests.java deleted file mode 100644 index 89d26f0da..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootKinesisEventHandlerTests.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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.aws; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; - -import com.amazonaws.kinesis.agg.AggRecord; -import com.amazonaws.kinesis.agg.RecordAggregator; -import com.amazonaws.services.lambda.runtime.events.KinesisEvent; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.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 static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Halvdan Hoem Grelland - */ -@Disabled -public class SpringBootKinesisEventHandlerTests { - - private static final ObjectMapper mapper = new ObjectMapper(); - - private SpringBootKinesisEventHandler handler; - - private static KinesisEvent asKinesisEvent(List payloads) { - KinesisEvent kinesisEvent = new KinesisEvent(); - - List kinesisEventRecords = new ArrayList<>(); - - for (Object payload : payloads) { - KinesisEvent.Record record = new KinesisEvent.Record(); - record.setData(asJsonByteBuffer(payload)); - - KinesisEvent.KinesisEventRecord kinesisEventRecord = new KinesisEvent.KinesisEventRecord(); - kinesisEventRecord.setKinesis(record); - - kinesisEventRecords.add(kinesisEventRecord); - } - - kinesisEvent.setRecords(kinesisEventRecords); - - return kinesisEvent; - } - - private static KinesisEvent asAggregatedKinesisEvent(List payloads) { - RecordAggregator aggregator = new RecordAggregator(); - - payloads.stream().map(SpringBootKinesisEventHandlerTests::asJsonByteBuffer) - .forEach(buffer -> { - try { - aggregator.addUserRecord("fakePartitionKey", buffer.array()); - } - catch (Exception e) { - fail("Creating aggregated record failed"); - } - }); - - AggRecord aggRecord = aggregator.clearAndGet(); - - KinesisEvent.Record record = new KinesisEvent.Record(); - record.setData(ByteBuffer.wrap(aggRecord.toRecordBytes())); - - KinesisEvent.KinesisEventRecord wrappingRecord = new KinesisEvent.KinesisEventRecord(); - wrappingRecord.setKinesis(record); - wrappingRecord.setEventVersion("1.0"); - - KinesisEvent event = new KinesisEvent(); - event.setRecords(singletonList(wrappingRecord)); - - return event; - } - - private static ByteBuffer asJsonByteBuffer(Object object) { - try { - return ByteBuffer.wrap(mapper.writeValueAsBytes(object)); - } - catch (JsonProcessingException e) { - fail("Setting up test data failed", e); - throw new RuntimeException(e); - } - } - - @Test - public void functionBeanHandlesKinesisEvent() throws Exception { - this.handler = new SpringBootKinesisEventHandler<>(FunctionConfig.class); - - KinesisEvent event = asKinesisEvent(singletonList(new Foo("foo"))); - - List output = this.handler.handleRequest(event, null); - - assertThat(output).containsExactly(new Bar("FOO")); - } - - @Test - public void functionBeanHandlesAggregatedKinesisEvent() throws Exception { - this.handler = new SpringBootKinesisEventHandler<>(FunctionConfig.class); - - List events = asList(new Foo("foo"), new Foo("bar"), new Foo("baz")); - KinesisEvent aggregatedEvent = asAggregatedKinesisEvent(events); - - List output = this.handler.handleRequest(aggregatedEvent, null); - - assertThat(output).containsExactly(new Bar("FOO"), new Bar("BAR"), - new Bar("BAZ")); - } - - @Test - public void functionMessageBean() throws Exception { - this.handler = new SpringBootKinesisEventHandler<>(FunctionMessageConfig.class); - - KinesisEvent event = asKinesisEvent(asList(new Foo("foo"), new Foo("bar"))); - - List output = this.handler.handleRequest(event, null); - - assertThat(output).containsExactly(new Bar("FOO"), new Bar("BAR")); - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionConfig { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionMessageConfig { - - @Bean - public Function, Bar> function() { - return foo -> new Bar(foo.getPayload().getValue().toUpperCase()); - } - - } - - protected 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; - } - - } - - protected static class Bar { - - private String value; - - public Bar() { - } - - public 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(this.value, bar.value); - } - - @Override - public int hashCode() { - return Objects.hash(this.value); - } - - } - -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandlerTests.java deleted file mode 100644 index 979ad0c1a..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootRequestHandlerTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2017-2020 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.aws; - -import java.util.function.Function; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.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 static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class SpringBootRequestHandlerTests { - - private SpringBootRequestHandler handler; - - @BeforeEach - public void after() { - System.clearProperty("spring.cloud.function.definition"); - } - - @Test - public void functionBean() throws Exception { - this.handler = new SpringBootRequestHandler(FunctionConfig.class); - Object output = this.handler.handleRequest(new Foo("foo"), null); - assertThat(output).isInstanceOf(Bar.class); - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionConfig { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - } - - protected static class Foo { - - private String value; - - public Foo(String value) { - this.value = value; - } - - public String getValue() { - return this.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 this.value; - } - - public void setValue(String value) { - this.value = value; - } - - } - -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandlerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandlerTests.java deleted file mode 100644 index c9aa2d935..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/SpringBootStreamHandlerTests.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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.aws; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.Map; -import java.util.function.Function; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.beans.factory.annotation.Autowired; -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.util.Assert; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - */ -public class SpringBootStreamHandlerTests { - - private SpringBootStreamHandler handler; - - @BeforeEach - public void before() { - System.clearProperty("function.name"); - } - - @Test - public void functionBeanWithJacksonConfig() throws Exception { - this.handler = new SpringBootStreamHandler(FunctionConfigWithJackson.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}"); - } - - @Test - public void functionBeanWithoutJacksonConfig() throws Exception { - this.handler = new SpringBootStreamHandler(FunctionConfigWithoutJackson.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}"); - } - - @Test - public void functionNonFluxBeanNoCatalog() throws Exception { - this.handler = new SpringBootStreamHandler(NoCatalogNonFluxFunctionConfig.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}"); - } - - @Test - public void functionFluxBeanNoCatalog() throws Exception { - this.handler = new SpringBootStreamHandler(NoCatalogFluxFunctionConfig.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}"); - } - - @Test - public void typelessFunctionConfig() throws Exception { - this.handler = new SpringBootStreamHandler(TypelessFunctionConfig.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"foo\"}"); - } - - @Test - public void inputStreamFunctionConfig() throws Exception { - this.handler = new SpringBootStreamHandler(InputStreamFunctionConfig.class); - this.handler.initialize(null); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - this.handler.handleRequest( - new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()), output, null); - assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}"); - } - - @Configuration - protected static class NoCatalogNonFluxFunctionConfig { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - } - - @Configuration - protected static class NoCatalogFluxFunctionConfig { - - @Bean - public Function, Flux> function() { - return flux -> flux.map(foo -> new Bar(foo.getValue().toUpperCase())); - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class FunctionConfigWithJackson { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class }) - protected static class FunctionConfigWithoutJackson { - - @Bean - public Function function() { - return foo -> new Bar(foo.getValue().toUpperCase()); - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class TypelessFunctionConfig { - - @Bean - public Function function() { - return value -> { - Assert.isTrue(value instanceof Map, "Expected value should be Map"); - return value; - }; - } - - } - - @Configuration - @Import({ ContextFunctionCatalogAutoConfiguration.class, - JacksonAutoConfiguration.class }) - protected static class InputStreamFunctionConfig { - - @Autowired - private ObjectMapper mapper; - - @Bean - public Function function() { - return value -> { - try { - Foo foo = this.mapper.readValue((InputStream) value, Foo.class); - return new Bar(foo.getValue().toUpperCase()); - } - catch (Exception e) { - throw new IllegalStateException("Failed test", e); - } - }; - } - - } - - protected static class Foo { - - private String value; - - public String getValue() { - return this.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 this.value; - } - - public void setValue(String value) { - this.value = value; - } - - } - -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java index 36f0cb35d..ce38c883e 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java @@ -47,7 +47,7 @@ import org.springframework.messaging.support.MessageBuilder; * @since 3.2 * */ -final class GrpcUtils { +public final class GrpcUtils { private static Log logger = LogFactory.getLog(GrpcUtils.class); From 53aa60d1187c098b5c719dd58a0f93340071b6f5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 9 Nov 2021 08:19:44 +0100 Subject: [PATCH 06/41] GH-764 Fix output header mapping timing Resolves #764 --- .../context/catalog/SimpleFunctionRegistry.java | 5 +++-- .../function/context/HeaderMappingTests.java | 17 ++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 863aa30f9..07ea3fb3e 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -355,8 +355,9 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect ? new BeanFactoryResolver(this.functionProperties.getApplicationContext()) : null; HeaderEnricher enricher = new HeaderEnricher(configuration.getOutputHeaderMappingExpression(), beanResolver); - FunctionInvocationWrapper w = new FunctionInvocationWrapper("outputHeaderEnricher", enricher, Message.class, Message.class); - composedFunction = (FunctionInvocationWrapper) w.andThen((Function) composedFunction); + Type mesageType = ResolvableType.forClassWithGenerics(Message.class, Object.class).getType(); + FunctionInvocationWrapper enricherWrapper = new FunctionInvocationWrapper("outputHeaderEnricher", enricher, mesageType, mesageType); + composedFunction = (FunctionInvocationWrapper) composedFunction.andThen((Function) enricherWrapper); composedFunction.functionDefinition = functionDefinition; } } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java index 16eaef369..67c3506e9 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/HeaderMappingTests.java @@ -180,17 +180,17 @@ public class HeaderMappingTests { SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run( "--logging.level.org.springframework.cloud.function=DEBUG", "--spring.main.lazy-initialization=true", - "--spring.cloud.function.configuration.foo.output-header-mapping-expression.key1='hello1'", - "--spring.cloud.function.configuration.foo.output-header-mapping-expression.key2=headers.contentType")) { + "--spring.cloud.function.configuration.foo.output-header-mapping-expression.keyOut1='hello1'", + "--spring.cloud.function.configuration.foo.output-header-mapping-expression.keyOut2=headers.contentType")) { FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); FunctionInvocationWrapper function = functionCatalog.lookup("foo"); Message result = (Message) function.apply(MessageBuilder.withPayload("helo") .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build()); - assertThat(result.getHeaders().containsKey("key1")).isTrue(); - assertThat(result.getHeaders().get("key1")).isEqualTo("hello1"); - assertThat(result.getHeaders().containsKey("key2")).isTrue(); - assertThat(result.getHeaders().get("key2")).isEqualTo("application/json"); + assertThat(result.getHeaders().containsKey("keyOut1")).isTrue(); + assertThat(result.getHeaders().get("keyOut1")).isEqualTo("hello1"); + assertThat(result.getHeaders().containsKey("keyOut2")).isTrue(); + assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json"); } } @@ -255,7 +255,10 @@ public class HeaderMappingTests { @Bean public Function, Message> foo() { - return x -> x; + return x -> { + assertThat(x.getHeaders().containsKey("keyOut1")).isFalse(); + return x; + }; } } } From db992a9f3ea430c97b6233f99bee5bca064eb6a9 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 10 Nov 2021 10:04:00 +0100 Subject: [PATCH 07/41] Remove FunctionInspector --- .../main/resources/META-INF/spring.factories | 4 +- .../context/catalog/FunctionInspector.java | 153 ------------------ .../catalog/SimpleFunctionRegistry.java | 9 +- 3 files changed, 2 insertions(+), 164 deletions(-) delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionInspector.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/resources/META-INF/spring.factories b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/resources/META-INF/spring.factories index b10fc836b..caf1d26e9 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/resources/META-INF/spring.factories @@ -1,4 +1,2 @@ org.springframework.context.ApplicationContextInitializer=\ -org.springframework.cloud.function.adapter.aws.CustomRuntimeInitializer -org.springframework.boot.env.EnvironmentPostProcessor=\ -org.springframework.cloud.function.adapter.aws.CustomRuntimeEnvironmentPostProcessor \ No newline at end of file +org.springframework.cloud.function.adapter.aws.CustomRuntimeInitializer \ No newline at end of file diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionInspector.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionInspector.java deleted file mode 100644 index 0b23146b0..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionInspector.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2012-2020 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.context.catalog; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; - -import net.jodah.typetools.TypeResolver; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - * - * @deprecated since 3.1 no longer used by the framework - */ -@Deprecated -public interface FunctionInspector { - - FunctionRegistration getRegistration(Object function); - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default boolean isMessage(Object function) { - if (function == null) { - return false; - } - - return ((FunctionInvocationWrapper) function).isInputTypeMessage(); - } - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default Class getInputType(Object function) { - if (function == null) { - return Object.class; - } - Type type = ((FunctionInvocationWrapper) function).getInputType(); - Class inputType; - if (type instanceof ParameterizedType) { - if (function != null && (((FunctionInvocationWrapper) function).isInputTypePublisher() || ((FunctionInvocationWrapper) function).isInputTypeMessage())) { - inputType = TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(type, 0), null); - } - else { - inputType = ((FunctionInvocationWrapper) function).getRawInputType(); - } - } - else { - inputType = type instanceof TypeVariable || type instanceof WildcardType ? Object.class : (Class) type; - } - return inputType; - } - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default Class getOutputType(Object function) { - if (function == null) { - return Object.class; - } - Type type = ((FunctionInvocationWrapper) function).getOutputType(); - Class outputType; - if (type instanceof ParameterizedType) { - if (function != null && ((FunctionInvocationWrapper) function).isOutputTypePublisher() || ((FunctionInvocationWrapper) function).isOutputTypeMessage()) { - outputType = TypeResolver.resolveRawClass(FunctionTypeUtils.getImmediateGenericType(type, 0), null); - } - else { - outputType = ((FunctionInvocationWrapper) function).getRawOutputType(); - } - } - else { - outputType = type instanceof TypeVariable || type instanceof WildcardType ? Object.class : (Class) type; - } - return outputType; - } - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default Class getInputWrapper(Object function) { - Class c = function == null ? Object.class : TypeResolver.resolveRawClass(((FunctionInvocationWrapper) function).getInputType(), null); - if (Flux.class.isAssignableFrom(c)) { - return c; - } - else if (Mono.class.isAssignableFrom(c)) { - return c; - } - else { - return this.getInputType(function); - } - } - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default Class getOutputWrapper(Object function) { - Class c = function == null ? Object.class : TypeResolver.resolveRawClass(((FunctionInvocationWrapper) function).getOutputType(), null); - if (Flux.class.isAssignableFrom(c)) { - return c; - } - else if (Mono.class.isAssignableFrom(c)) { - return c; - } - else { - return this.getOutputType(function); - } - } - - /** - * - * @deprecated since 3.1 no longer used by the framework - */ - @Deprecated - default String getName(Object function) { - if (function == null) { - return null; - } - return ((FunctionInvocationWrapper) function).getFunctionDefinition(); - } - -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 07ea3fb3e..2e22f46c4 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -82,7 +82,7 @@ import org.springframework.util.StringUtils; * @author Oleg Zhurakousky * */ -public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspector { +public class SimpleFunctionRegistry implements FunctionRegistry { protected Log logger = LogFactory.getLog(this.getClass()); /* * - do we care about FunctionRegistration after it's been registered? What additional value does it bring? @@ -133,13 +133,6 @@ public class SimpleFunctionRegistry implements FunctionRegistry, FunctionInspect } } - @Override - public FunctionRegistration getRegistration(Object function) { - throw new UnsupportedOperationException("FunctionInspector is deprecated. There is no need " - + "to access FunctionRegistration directly since you can interogate the actual " - + "looked-up function (see FunctionInvocationWrapper."); - } - public SimpleFunctionRegistry(ConversionService conversionService, CompositeMessageConverter messageConverter, JsonMapper jsonMapper) { this(conversionService, messageConverter, jsonMapper, null, null); } From e297a36a0610c89a203def225fa0063923a7ddfc Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 10 Nov 2021 15:21:31 +0100 Subject: [PATCH 08/41] Remove FunctionType and dependencies on it --- .../adapter/azure/FunctionInvoker.java | 3 +- ...tractSpringFunctionAdapterInitializer.java | 4 +- .../context/FunctionRegistration.java | 114 ++-- .../cloud/function/context/FunctionType.java | 549 ------------------ .../context/FunctionalSpringApplication.java | 2 +- .../catalog/FunctionTypeConversionHelper.java | 12 +- .../context/catalog/FunctionTypeUtils.java | 77 ++- .../catalog/SimpleFunctionRegistry.java | 2 +- .../function/context/FunctionTypeTests.java | 346 ----------- ...SpringFunctionAdapterInitializerTests.java | 239 -------- ...BeanFactoryAwareFunctionRegistryTests.java | 20 +- .../catalog/FunctionTypeUtilsTests.java | 31 +- .../catalog/SimpleFunctionRegistryTests.java | 75 ++- ...ontextFunctionCatalogInitializerTests.java | 20 +- .../java/example/FunctionConfiguration.java | 5 +- .../java/com/example/LambdaApplication.java | 5 +- .../com/example/demo/DemoApplication.java | 4 +- .../FunctionExporterAutoConfiguration.java | 11 +- .../test/FunctionalExporterTests.java | 17 +- .../FunctionEndpointInitializerTests.java | 13 +- 20 files changed, 243 insertions(+), 1306 deletions(-) delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionTypeTests.java delete mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java index 8081c234c..e8b2eb1bb 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java @@ -44,7 +44,6 @@ import org.springframework.boot.WebApplicationType; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; @@ -152,7 +151,7 @@ public class FunctionInvoker { Type type = FunctionContextUtils. findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory()); - functionRegistration = functionRegistration.type(new FunctionType(type)); + functionRegistration = functionRegistration.type(type); ((FunctionRegistry) FUNCTION_CATALOG).register(functionRegistration); } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java index 8aea9e37f..95b64e5d2 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java @@ -141,7 +141,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo return FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(((FunctionInvocationWrapper) func).getInputType())); } if (functionRegistration != null) { - return functionRegistration.getType().getInputType(); + return FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(functionRegistration.getType())); } return Object.class; } @@ -287,7 +287,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo Type type = FunctionContextUtils. findType(name, this.context.getBeanFactory()); - this.functionRegistration = functionRegistration.type(new FunctionType(type)); + this.functionRegistration = functionRegistration.type(type); ((FunctionRegistry) this.catalog).register(functionRegistration); return this.catalog.lookup(name); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java index c579273dd..8be661d2f 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-2021 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. @@ -27,19 +27,13 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import net.jodah.typetools.TypeResolver; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - import org.springframework.beans.factory.BeanNameAware; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.cloud.function.context.config.RoutingFunction; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; - - /** * @param target type * @author Dave Syer @@ -63,7 +57,7 @@ public class FunctionRegistration implements BeanNameAware { private T target; - private FunctionType type; + private Type type; /** * Creates instance of FunctionRegistration. @@ -97,7 +91,7 @@ public class FunctionRegistration implements BeanNameAware { this.names.addAll(names); } - public FunctionType getType() { + public Type getType() { return this.type; } @@ -111,25 +105,33 @@ public class FunctionRegistration implements BeanNameAware { } public FunctionRegistration type(Type type) { - return type(FunctionType.of(type)); - } - - public FunctionRegistration type(FunctionType type) { - - Type t = FunctionTypeUtils.discoverFunctionTypeFromClass(this.target.getClass()); - if (t == null) { // only valid for Kafka Stream KStream[] return type. + Type discoveredFunctionType = FunctionTypeUtils.discoverFunctionTypeFromClass(this.target.getClass()); + if (discoveredFunctionType == null) { // only valid for Kafka Stream KStream[] return type. return null; } - FunctionType discoveredFunctionType = FunctionType.of(t); - Class inputType = TypeResolver.resolveRawClass(discoveredFunctionType.getInputType(), null); - Class outputType = TypeResolver.resolveRawClass(discoveredFunctionType.getOutputType(), null); - - if (!(inputType.isAssignableFrom(TypeResolver.resolveRawClass(type.getInputType(), null)) - && outputType.isAssignableFrom(TypeResolver.resolveRawClass(type.getOutputType(), null)))) { - throw new IllegalStateException("Discovered function type does not match provided function type. Discovered: " - + discoveredFunctionType + "; Provided: " + type); - } this.type = type; + + Class inputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(discoveredFunctionType)); + Class outputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(discoveredFunctionType)); + + if (inputType != null && inputType != Object.class && outputType != null && outputType != Object.class) { + Assert.isTrue((inputType.isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(type))) + && outputType.isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(type)))), + "Discovered function type does not match provided function type. Discovered: " + + discoveredFunctionType + "; Provided: " + type); + } + else if (inputType == null && outputType != Object.class) { + Assert.isTrue(outputType.isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(type))), + "Discovered function type does not match provided function type. Discovered: " + + discoveredFunctionType + "; Provided: " + type); + } + else if (outputType == null && inputType != Object.class) { + Assert.isTrue(inputType.isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(type))), + "Discovered function type does not match provided function type. Discovered: " + + discoveredFunctionType + "; Provided: " + type); + } + + return this; } @@ -167,30 +169,30 @@ public class FunctionRegistration implements BeanNameAware { * */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public FunctionRegistration wrap() { - this.isFunctionSignatureSupported(); - FunctionRegistration result; - if (this.type == null) { - result = (FunctionRegistration) this; - } - else if (this.target instanceof RoutingFunction) { - S target = (S) this.target; - result = new FunctionRegistration(target); - result.type(this.type.getType()); - result = result.target(target).names(this.names) - .type(result.type.wrap(Flux.class)).properties(this.properties); - } - else { - S target = (S) this.target; - result = new FunctionRegistration(target); - result.type(this.type.getType()); - result = result.target(target).names(this.names) - .type(result.type.wrap(Flux.class)).properties(this.properties); - } - - return result; - } +// @SuppressWarnings({ "unchecked", "rawtypes" }) +// public FunctionRegistration wrap() { +// this.isFunctionSignatureSupported(); +// FunctionRegistration result; +// if (this.type == null) { +// result = (FunctionRegistration) this; +// } +// else if (this.target instanceof RoutingFunction) { +// S target = (S) this.target; +// result = new FunctionRegistration(target); +// result.type(this.type.getType()); +// result = result.target(target).names(this.names) +// .type(result.type.wrap(Flux.class)).properties(this.properties); +// } +// else { +// S target = (S) this.target; +// result = new FunctionRegistration(target); +// result.type(this.type.getType()); +// result = result.target(target).names(this.names) +// .type(result.type.wrap(Flux.class)).properties(this.properties); +// } +// +// return result; +// } @Override public void setBeanName(String name) { @@ -199,12 +201,12 @@ public class FunctionRegistration implements BeanNameAware { } } - private void isFunctionSignatureSupported() { - if (type != null) { - Assert.isTrue(!(Mono.class.isAssignableFrom(this.type.getOutputWrapper()) - && Mono.class.isAssignableFrom(this.type.getInputWrapper())), - "Function is not supported."); - } - } +// private void isFunctionSignatureSupported() { +// if (type != null) { +// Assert.isTrue(!(Mono.class.isAssignableFrom(this.type.getOutputWrapper()) +// && Mono.class.isAssignableFrom(this.type.getInputWrapper())), +// "Function is not supported."); +// } +// } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java deleted file mode 100644 index 9d9723361..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionType.java +++ /dev/null @@ -1,549 +0,0 @@ -/* - * 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.context; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import reactor.core.publisher.Flux; - - -import org.springframework.core.ResolvableType; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.messaging.Message; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - * - */ -public class FunctionType { - - /** - * Unclassified function types. - */ - public static FunctionType UNCLASSIFIED = new FunctionType(ResolvableType - .forClassWithGenerics(Function.class, Object.class, Object.class).getType()); - - private static List transformers; - - private Type type; - - private Class inputType; - - private Class outputType; - - private Class inputWrapper; - - private Class outputWrapper; - - private boolean message; - - public FunctionType(Type type) { - this.type = functionType(type); - this.inputWrapper = findType(ParamType.INPUT_WRAPPER); - this.outputWrapper = findType(ParamType.OUTPUT_WRAPPER); - this.inputType = findType(ParamType.INPUT); - this.outputType = findType(ParamType.OUTPUT); - this.message = messageType(); - resetType(); - } - - /* - * Experimental for now. Used (reflectively) in FunctionCreatorConfiguration to effectively - * map an existing FunctionType created by one class loader to another. - */ - @SuppressWarnings("unused") // it is used - private FunctionType(Object functionType) throws Exception { - Field[] fields = functionType.getClass().getDeclaredFields(); - for (Field field : fields) { - if (!Modifier.isStatic(field.getModifiers())) { - field.setAccessible(true); - Field thisField = ReflectionUtils.findField(this.getClass(), field.getName()); - thisField.setAccessible(true); - thisField.set(this, field.get(functionType)); - } - } - } - - public static boolean isWrapper(Type type) { - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - if (transformers == null) { - transformers = new ArrayList<>(); - transformers.addAll( - SpringFactoriesLoader.loadFactories(WrapperDetector.class, null)); - } - for (WrapperDetector transformer : transformers) { - if (transformer.isWrapper(type)) { - return true; - } - } - return false; - } - - public static FunctionType of(Type function) { - FunctionType ft = new FunctionType(function); - if (!ft.isWrapper() && !(ft.type instanceof ParameterizedType)) { - Type[] genericInterfaces = ((Class) function).getGenericInterfaces(); - if (!ObjectUtils.isEmpty(genericInterfaces)) { - ft.type = genericInterfaces[0]; - } - } - return ft; - } - - public static FunctionType from(Class input) { - return new FunctionType(ResolvableType - .forClassWithGenerics(Function.class, input, Object.class).getType()); - } - - public static FunctionType supplier(Class input) { - return new FunctionType( - ResolvableType.forClassWithGenerics(Supplier.class, input).getType()); - } - - public static FunctionType consumer(Class input) { - return new FunctionType( - ResolvableType.forClassWithGenerics(Consumer.class, input).getType()); - } - - public static FunctionType compose(FunctionType input, FunctionType output) { - ResolvableType inputGeneric = input(input); - ResolvableType outputGeneric = output(output); - if (!isWrapper(outputGeneric.getType())) { - ResolvableType inputOutput = output(input); - if (isWrapper(inputOutput.getType())) { - outputGeneric = wrap(input, - extractClass(inputOutput.getType(), ParamType.OUTPUT_WRAPPER), - extractClass(outputGeneric.getType(), ParamType.OUTPUT)); - } - } - return new FunctionType(ResolvableType - .forClassWithGenerics(Function.class, inputGeneric, outputGeneric) - .getType()); - } - - public Type getType() { - return this.type; - } - - public Class getInputWrapper() { - return this.inputWrapper; - } - - public Class getOutputWrapper() { - return this.outputWrapper; - } - - public Class getInputType() { - return this.inputType; - } - - public Class getOutputType() { - return this.outputType; - } - - public boolean isMessage() { - return this.message; - } - - public boolean isWrapper() { - return isWrapper(getInputWrapper()) || isWrapper(getOutputWrapper()); - } - - public FunctionType to(Class output) { - ResolvableType inputGeneric = input(this); - ResolvableType outputGeneric = output(output); - return new FunctionType(ResolvableType - .forClassWithGenerics(Function.class, inputGeneric, outputGeneric) - .getType()); - } - - public FunctionType message() { - if (isMessage()) { - return this; - } - ResolvableType inputGeneric = message(getInputType()); - ResolvableType outputGeneric = message(getOutputType()); - if (isWrapper(getInputWrapper())) { - inputGeneric = ResolvableType.forClassWithGenerics(getInputWrapper(), - inputGeneric); - outputGeneric = ResolvableType.forClassWithGenerics(getInputWrapper(), - outputGeneric); - } - return new FunctionType(ResolvableType - .forClassWithGenerics(Function.class, inputGeneric, outputGeneric) - .getType()); - } - - public FunctionType wrap(Class input, Class output) { - if (!isWrapper(input) && !isWrapper(output)) { - return this; - } - else if (isWrapper(input) && isWrapper(output)) { - if (input.isAssignableFrom(getInputWrapper()) - && output.isAssignableFrom(getOutputWrapper())) { - return this; - } - return new FunctionType(ResolvableType.forClassWithGenerics(Function.class, - wrapper(input, getInputType()), wrapper(output, getOutputType())) - .getType()); - } - else { - throw new IllegalArgumentException("Both wrapper types must be wrappers in (" - + input + ", " + output + ")"); - } - } - - public FunctionType wrap(Class wrapper) { - return wrap(wrapper, wrapper); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((this.inputType == null) ? 0 : this.inputType.toString().hashCode()); - result = prime * result + ((this.inputWrapper == null) ? 0 - : this.inputWrapper.toString().hashCode()); - result = prime * result + (this.message ? 1231 : 1237); - result = prime * result - + ((this.outputType == null) ? 0 : this.outputType.toString().hashCode()); - result = prime * result + ((this.outputWrapper == null) ? 0 - : this.outputWrapper.toString().hashCode()); - return result; - } - - public String toString() { - if (this.inputType == Void.class) { - return this.type.toString() + ", which is effectively a Supplier<" - + this.outputType + ">"; - } - else if (this.outputType == Void.class) { - return this.type.toString() + ", which is effectively a Consumer<" - + this.inputType + ">"; - } - return this.type.toString(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - FunctionType other = (FunctionType) obj; - if (this.inputType == null) { - if (other.inputType != null) { - return false; - } - } - else if (!this.inputType.toString().equals(other.inputType.toString())) { - return false; - } - if (this.inputWrapper == null) { - if (other.inputWrapper != null) { - return false; - } - } - else if (!this.inputWrapper.toString().equals(other.inputWrapper.toString())) { - return false; - } - if (this.message != other.message) { - return false; - } - if (this.outputType == null) { - if (other.outputType != null) { - return false; - } - } - else if (!this.outputType.toString().equals(other.outputType.toString())) { - return false; - } - if (this.outputWrapper == null) { - if (other.outputWrapper != null) { - return false; - } - } - else if (!this.outputWrapper.toString().equals(other.outputWrapper.toString())) { - return false; - } - return true; - } - - private static ResolvableType wrap(FunctionType input, Class wrapper, - Class type) { - return input.isMessage() ? wrap(wrapper, message(type)) - : ResolvableType.forClassWithGenerics(wrapper, type); - } - - private static ResolvableType wrap(Class wrapper, ResolvableType type) { - return ResolvableType.forClassWithGenerics(wrapper, type); - } - - private static ResolvableType message(Class type) { - return ResolvableType.forClassWithGenerics(Message.class, type); - } - - private static ResolvableType input(FunctionType type) { - return type.input(type.getInputType()); - } - - private static ResolvableType output(FunctionType type) { - return type.output(type.getOutputType()); - } - - private static Class extractClass(Type param, ParamType paramType) { - if (param instanceof ParameterizedType) { - ParameterizedType concrete = (ParameterizedType) param; - param = concrete.getRawType(); - } - if (param == null) { - // Last ditch attempt to guess: Flux - if (paramType.isWrapper()) { - param = Flux.class; - } - else { - param = String.class; - } - } - Class result = param instanceof Class ? (Class) param : null; - // TODO: cache result - return result; - } - - private ResolvableType wrapper(Class wrapper, Class type) { - return wrap(this, wrapper, type); - } - - private ResolvableType output(Class type) { - ResolvableType generic; - ResolvableType raw = ResolvableType.forClass(type); - if (isMessage()) { - raw = ResolvableType.forClassWithGenerics(Message.class, raw); - } - if (FunctionType.isWrapper(getOutputWrapper())) { - generic = ResolvableType.forClassWithGenerics(getOutputWrapper(), raw); - } - else { - generic = raw; - } - return generic; - } - - private ResolvableType input(Class type) { - ResolvableType generic; - ResolvableType raw = ResolvableType.forClass(type); - if (isMessage()) { - raw = ResolvableType.forClassWithGenerics(Message.class, raw); - } - if (FunctionType.isWrapper(getInputWrapper())) { - generic = ResolvableType.forClassWithGenerics(getInputWrapper(), raw); - } - else { - generic = raw; - } - return generic; - } - - private Class findType(ParamType paramType) { - int index = paramType.isOutput() ? 1 : 0; - Type type = this.type; - if (Supplier.class.isAssignableFrom(extractClass(this.type, null))) { - if (paramType.isInput()) { - return Void.class; - } - } - boolean found = false; - while (!found && type instanceof Class && type != Object.class) { - Class clz = (Class) type; - for (Type iface : clz.getGenericInterfaces()) { - if (iface.getTypeName().startsWith("java.util.function")) { - type = iface; - found = true; - break; - } - } - if (!found) { - type = clz.getSuperclass(); - } - } - Type param = extractType(type, paramType, index); - if (param != null) { - Class result = extractClass(param, paramType); - if (result != null) { - return result; - } - } - return Object.class; - } - - private void resetType() { - if (!this.type.getTypeName().contains("EnhancerBySpringCGLIB")) { - return; - } - Type type = this.type; - - boolean found = false; - while (!found && type instanceof Class && type != Object.class) { - Class clz = (Class) type; - for (Type iface : clz.getGenericInterfaces()) { - if (iface.getTypeName().startsWith("java.util.function")) { - type = iface; - found = true; - break; - } - } - if (!found) { - type = clz.getSuperclass(); - } - } - this.type = type; - } - - private Type extractType(Type type, ParamType paramType, int index) { - Type param; - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - if (parameterizedType.getActualTypeArguments().length == 1) { - if (isVoid(parameterizedType, paramType)) { - return Void.class; - } - // There's only one - index = 0; - } - Type typeArgumentAtIndex = parameterizedType.getActualTypeArguments()[index]; - if (typeArgumentAtIndex instanceof ParameterizedType - && !paramType.isWrapper()) { - if (FunctionType.isWrapper( - ((ParameterizedType) typeArgumentAtIndex).getRawType())) { - param = ((ParameterizedType) typeArgumentAtIndex) - .getActualTypeArguments()[0]; - param = extractNestedType(paramType, param); - } - else { - param = extractNestedType(paramType, typeArgumentAtIndex); - } - } - else { - param = extractNestedType(paramType, typeArgumentAtIndex); - } - } - else { - if (type != null) { - Type[] interfaces = ((Class) type).getGenericInterfaces(); - for (Type ifc : interfaces) { - Type value = extractType(ifc, paramType, index); - if (value != Object.class) { - return value; - } - } - } - param = Object.class; - } - return param; - } - - private boolean isVoid(ParameterizedType parameterizedType, ParamType paramType) { - Class rawType = extractClass(parameterizedType.getRawType(), paramType); - if (Consumer.class.isAssignableFrom(rawType) && paramType.isOutput()) { - return true; - } - if (Supplier.class.isAssignableFrom(rawType) && paramType.isInput()) { - return true; - } - return false; - } - - private Type extractNestedType(ParamType paramType, Type param) { - if (!paramType.isInnerWrapper() && param instanceof ParameterizedType) { - if (((ParameterizedType) param).getRawType().getTypeName() - .startsWith(Message.class.getName())) { - param = ((ParameterizedType) param).getActualTypeArguments()[0]; - } - } - return param; - } - - private Type functionType(Type type) { - if (Supplier.class.isAssignableFrom(extractClass(type, ParamType.OUTPUT))) { - Type product = extractType(type, ParamType.OUTPUT, 0); - Class output = extractClass(product, ParamType.OUTPUT); - if (output != null) { - if (FunctionRegistration.class.isAssignableFrom(output)) { - type = extractType(product, ParamType.OUTPUT, 0); - } - else if (Function.class.isAssignableFrom(output) - || Supplier.class.isAssignableFrom(output) - || Consumer.class.isAssignableFrom(output)) { - type = product; - } - } - } - return type; - } - - private boolean messageType() { - Class inputType = findType(ParamType.INPUT_INNER_WRAPPER); - Class outputType = findType(ParamType.OUTPUT_INNER_WRAPPER); - return inputType.getName().startsWith(Message.class.getName()) - || Message.class.isAssignableFrom(inputType) - || outputType.getName().startsWith(Message.class.getName()) - || Message.class.isAssignableFrom(outputType); - } - - enum ParamType { - - INPUT, OUTPUT, INPUT_WRAPPER, OUTPUT_WRAPPER, INPUT_INNER_WRAPPER, OUTPUT_INNER_WRAPPER; - - public boolean isOutput() { - return this == OUTPUT || this == OUTPUT_WRAPPER - || this == OUTPUT_INNER_WRAPPER; - } - - public boolean isInput() { - return this == INPUT || this == INPUT_WRAPPER || this == INPUT_INNER_WRAPPER; - } - - public boolean isWrapper() { - return this == OUTPUT_WRAPPER || this == INPUT_WRAPPER; - } - - public boolean isInnerWrapper() { - return this == OUTPUT_INNER_WRAPPER || this == INPUT_INNER_WRAPPER; - } - - } - -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionalSpringApplication.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionalSpringApplication.java index eebf2f0d2..04365495a 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionalSpringApplication.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionalSpringApplication.java @@ -153,7 +153,7 @@ public class FunctionalSpringApplication context.registerBean("function", FunctionRegistration.class, () -> new FunctionRegistration<>( handler(context, function, functionType)) - .type(FunctionType.of(functionType))); + .type(functionType)); } private Object handler(GenericApplicationContext generic, Object handler, diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeConversionHelper.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeConversionHelper.java index 67e871b8e..f5b0a4840 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeConversionHelper.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeConversionHelper.java @@ -71,12 +71,12 @@ class FunctionTypeConversionHelper { this.conversionService = conversionService; this.messageConverter = messageConverter; this.functionRegistration = functionRegistration; - if ((this.functionRegistration.getType().getType()) instanceof ParameterizedType) { - this.functionArgumentTypes = ((ParameterizedType) this.functionRegistration.getType().getType()) + if ((this.functionRegistration.getType()) instanceof ParameterizedType) { + this.functionArgumentTypes = ((ParameterizedType) this.functionRegistration.getType()) .getActualTypeArguments(); } else { - this.functionArgumentTypes = new Type[] { this.functionRegistration.getType().getInputType() }; + this.functionArgumentTypes = new Type[] { FunctionTypeUtils.getInputType(this.functionRegistration.getType()) }; } } @@ -227,7 +227,7 @@ class FunctionTypeConversionHelper { } } else { - Assert.isTrue(!Publisher.class.isAssignableFrom(this.functionRegistration.getType().getInputWrapper()), + Assert.isTrue(!FunctionTypeUtils.isPublisher(FunctionTypeUtils.getInputType(this.functionRegistration.getType())), "Invoking reactive function as imperative is not allowed. Function name(s): " + this.functionRegistration.getNames()); incoming = this.doConvertArgument(incoming, targetType, actualType); @@ -244,7 +244,7 @@ class FunctionTypeConversionHelper { : Flux.from((Publisher) incoming).map(value -> this.messageConverter.toMessage(value, headers)); } else { - Assert.isTrue(!Publisher.class.isAssignableFrom(this.functionRegistration.getType().getInputWrapper()), + Assert.isTrue(!FunctionTypeUtils.isPublisher(FunctionTypeUtils.getInputType(this.functionRegistration.getType())), "Invoking reactive function as imperative is not allowed. Function name(s): " + this.functionRegistration.getNames()); incoming = this.messageConverter.toMessage(incoming, headers); @@ -296,7 +296,7 @@ class FunctionTypeConversionHelper { incomingValue = incomingMessage; } else { - incomingValue = this.messageConverter.fromMessage((Message) incomingMessage, targetType); + incomingValue = this.messageConverter.fromMessage(incomingMessage, targetType); } } return incomingValue; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index be0b9264e..0574ae627 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java @@ -41,7 +41,6 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.config.FunctionContextUtils; import org.springframework.cloud.function.context.config.RoutingFunction; import org.springframework.context.support.GenericApplicationContext; @@ -68,6 +67,21 @@ public final class FunctionTypeUtils { } + public static Type functionType(Type input, Type output) { + return ResolvableType.forClassWithGenerics(Function.class, + ResolvableType.forType(input), ResolvableType.forType(output)).getType(); + } + + public static Type consumerType(Type input) { + return ResolvableType.forClassWithGenerics(Consumer.class, + ResolvableType.forType(input)).getType(); + } + + public static Type supplierType(Type output) { + return ResolvableType.forClassWithGenerics(Supplier.class, + ResolvableType.forType(output)).getType(); + } + /** * Will return 'true' if the provided type is a {@link Collection} type. * This also includes collections wrapped in {@link Message}. For example, @@ -176,6 +190,37 @@ public final class FunctionTypeUtils { return null; } + /** + * Discovers the function {@link Type} based on the signature of a factory method. + * For example, given the following method {@code Function, Message> uppercase()} of + * class Foo - {@code Type type = discoverFunctionTypeFromFunctionFactoryMethod(Foo.class, "uppercase");} + * + * @param clazz instance of Class containing the factory method + * @param methodName factory method name + * @return type of the function + */ + public static Type discoverFunctionTypeFromFunctionFactoryMethod(Class clazz, String methodName) { + return discoverFunctionTypeFromFunctionFactoryMethod(ReflectionUtils.findMethod(clazz, methodName)); + } + + /** + * Discovers the function {@link Type} based on the signature of a factory method. + * For example, given the following method {@code Function, Message> uppercase()} of + * class Foo - {@code Type type = discoverFunctionTypeFromFunctionFactoryMethod(Foo.class, "uppercase");} + * + * @param method factory method + * @return type of the function + */ + public static Type discoverFunctionTypeFromFunctionFactoryMethod(Method method) { + return method.getGenericReturnType(); + } + + /** + * Unlike {@link #discoverFunctionTypeFromFunctionFactoryMethod(Class, String)}, this method discovers function + * type from the well known method of Function(apply), Supplier(get) or Consumer(accept). + * @param functionMethod functional method + * @return type of the function + */ public static Type discoverFunctionTypeFromFunctionMethod(Method functionMethod) { Assert.isTrue( functionMethod.getName().equals("apply") || @@ -223,6 +268,26 @@ public final class FunctionTypeUtils { return outputCount; } + /** + * In the event the input type is {@link ParameterizedType} this method returns its generic type. + * @param functionType instance of function type + * @return generic type or input type + */ + public static Type getComponentTypeOfInputType(Type functionType) { + Type inputType = getInputType(functionType); + return getImmediateGenericType(inputType, 0); + } + + /** + * In the event the output type is {@link ParameterizedType} this method returns its generic type. + * @param functionType instance of function type + * @return generic type or output type + */ + public static Type getComponentTypeOfOutputType(Type functionType) { + Type inputType = getOutputType(functionType); + return getImmediateGenericType(inputType, 0); + } + /** * Returns input type of function type that represents Function or Consumer. * @param functionType the Type of Function or Consumer @@ -253,15 +318,15 @@ public final class FunctionTypeUtils { @SuppressWarnings("rawtypes") public static Type discoverFunctionType(Object function, String functionName, GenericApplicationContext applicationContext) { if (function instanceof RoutingFunction) { - return FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), functionName)).getType(); + return FunctionContextUtils.findType(applicationContext.getBeanFactory(), functionName); } else if (function instanceof FunctionRegistration) { - return ((FunctionRegistration) function).getType().getType(); + return ((FunctionRegistration) function).getType(); } if (applicationContext.containsBean(functionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX)) { // for Kotlin primarily FunctionRegistration fr = applicationContext .getBean(functionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX, FunctionRegistration.class); - return fr.getType().getType(); + return fr.getType(); } boolean beanDefinitionExists = false; @@ -277,13 +342,13 @@ public final class FunctionTypeUtils { if (beanDefinitionExists) { Type t = FunctionTypeUtils.getImmediateGenericType(type, 0); if (t == null || t == Object.class) { - type = FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), functionBeanDefinitionName)).getType(); + type = FunctionContextUtils.findType(applicationContext.getBeanFactory(), functionBeanDefinitionName); } } else if (!(type instanceof ParameterizedType)) { String beanDefinitionName = discoverBeanDefinitionNameByQualifier(applicationContext.getBeanFactory(), functionName); if (StringUtils.hasText(beanDefinitionName)) { - type = FunctionType.of(FunctionContextUtils.findType(applicationContext.getBeanFactory(), beanDefinitionName)).getType(); + type = FunctionContextUtils.findType(applicationContext.getBeanFactory(), beanDefinitionName); } } return type; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 2e22f46c4..a6e13f4f4 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -262,7 +262,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry { .findFirst() .orElseGet(() -> null); FunctionInvocationWrapper function = functionRegistration != null - ? this.invocationWrapperInstance(functionName, functionRegistration.getTarget(), functionRegistration.getType().getType()) + ? this.invocationWrapperInstance(functionName, functionRegistration.getTarget(), functionRegistration.getType()) : null; if (functionRegistration != null && functionRegistration.getProperties().containsKey("singleton")) { try { diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionTypeTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionTypeTests.java deleted file mode 100644 index 94b0abb81..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/FunctionTypeTests.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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.context; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.util.function.Tuple2; -import reactor.util.function.Tuple3; - -import org.springframework.core.ResolvableType; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class FunctionTypeTests { - - @Test - public void functionWithTuples() { - FunctionType functionType = FunctionType.of(MyFunction.class); - assertThat(functionType.getType()).isInstanceOf(ParameterizedType.class); - } - - @Test - public void plainFunction() { - FunctionType function = new FunctionType(IntegerToString.class); - assertThat(function.getInputType()).isEqualTo(Integer.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Integer.class); - assertThat(function.getOutputWrapper()).isEqualTo(String.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void supplierOfRegistration() { - FunctionType function = new FunctionType( - SupplierOfRegistrationOfIntegerToString.class); - assertThat(function.getInputType()).isEqualTo(Integer.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Integer.class); - assertThat(function.getOutputWrapper()).isEqualTo(String.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void supplier() { - FunctionType function = new FunctionType(SupplierOfIntegerToString.class); - assertThat(function.getInputType()).isEqualTo(Integer.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Integer.class); - assertThat(function.getOutputWrapper()).isEqualTo(String.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void genericFunction() { - FunctionType function = new FunctionType(StringToMap.class); - assertThat(function.getInputType()).isEqualTo(String.class); - assertThat(function.getOutputType()).isEqualTo(Map.class); - assertThat(function.getInputWrapper()).isEqualTo(String.class); - assertThat(function.getOutputWrapper()).isEqualTo(Map.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoFunction() { - FunctionType function = new FunctionType(FooToFoo.class); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Bar.class); - assertThat(function.getInputWrapper()).isEqualTo(Foo.class); - assertThat(function.getOutputWrapper()).isEqualTo(Bar.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void fluxFunction() { - FunctionType function = new FunctionType(FluxToFlux.class); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Bar.class); - assertThat(function.getInputWrapper()).isEqualTo(Flux.class); - assertThat(function.getOutputWrapper()).isEqualTo(Flux.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void fluxMessageFunction() { - FunctionType function = new FunctionType(FluxMessageToFluxMessage.class); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Bar.class); - assertThat(function.getInputWrapper()).isEqualTo(Flux.class); - assertThat(function.getOutputWrapper()).isEqualTo(Flux.class); - assertThat(function.isMessage()).isEqualTo(true); - } - - @Test - public void plainFunctionFromType() { - Type type = ResolvableType - .forClassWithGenerics(Function.class, Integer.class, String.class) - .getType(); - FunctionType function = new FunctionType(type); - assertThat(function.getInputType()).isEqualTo(Integer.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Integer.class); - assertThat(function.getOutputWrapper()).isEqualTo(String.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoConsumerFromType() { - Type type = ResolvableType.forClassWithGenerics(Consumer.class, Foo.class) - .getType(); - FunctionType function = new FunctionType(type); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Void.class); - assertThat(function.getInputWrapper()).isEqualTo(Foo.class); - assertThat(function.getOutputWrapper()).isEqualTo(Void.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoSupplierFromType() { - Type type = ResolvableType.forClassWithGenerics(Supplier.class, Foo.class) - .getType(); - FunctionType function = new FunctionType(type); - assertThat(function.getInputType()).isEqualTo(Void.class); - assertThat(function.getOutputType()).isEqualTo(Foo.class); - assertThat(function.getInputWrapper()).isEqualTo(Void.class); - assertThat(function.getOutputWrapper()).isEqualTo(Foo.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoSupplierFrom() { - FunctionType function = new FunctionType(Supplier.class).to(Foo.class); - assertThat(function.getInputType()).isEqualTo(Void.class); - assertThat(function.getOutputType()).isEqualTo(Foo.class); - assertThat(function.getInputWrapper()).isEqualTo(Void.class); - assertThat(function.getOutputWrapper()).isEqualTo(Foo.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoSupplier() { - FunctionType function = FunctionType.supplier(Foo.class); - assertThat(function.getInputType()).isEqualTo(Void.class); - assertThat(function.getOutputType()).isEqualTo(Foo.class); - assertThat(function.getInputWrapper()).isEqualTo(Void.class); - assertThat(function.getOutputWrapper()).isEqualTo(Foo.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void pojoConsumer() { - FunctionType function = FunctionType.consumer(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Void.class); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputWrapper()).isEqualTo(Void.class); - assertThat(function.getInputWrapper()).isEqualTo(Foo.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void plainFunctionFromApi() { - FunctionType function = FunctionType.from(Integer.class).to(String.class); - assertThat(function.getInputType()).isEqualTo(Integer.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Integer.class); - assertThat(function.getOutputWrapper()).isEqualTo(String.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void fluxMessageFunctionFromType() { - Type type = ResolvableType - .forClassWithGenerics(Function.class, - ResolvableType.forClassWithGenerics( - Flux.class, - ResolvableType.forClassWithGenerics(Message.class, - Foo.class)), - ResolvableType.forClassWithGenerics(Flux.class, ResolvableType - .forClassWithGenerics(Message.class, Bar.class))) - .getType(); - FunctionType function = new FunctionType(type); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Bar.class); - assertThat(function.getInputWrapper()).isEqualTo(Flux.class); - assertThat(function.getOutputWrapper()).isEqualTo(Flux.class); - assertThat(function.isMessage()).isEqualTo(true); - } - - @Test - public void fluxMessageFunctionFromApi() { - FunctionType function = FunctionType.from(Foo.class).to(Bar.class).message() - .wrap(Flux.class); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(Bar.class); - assertThat(function.getInputWrapper()).isEqualTo(Flux.class); - assertThat(function.getOutputWrapper()).isEqualTo(Flux.class); - assertThat(function.isMessage()).isEqualTo(true); - } - - @Test - public void compose() { - FunctionType input = FunctionType.from(Foo.class).to(Bar.class).wrap(Flux.class); - FunctionType output = FunctionType.from(Bar.class).to(String.class); - FunctionType function = FunctionType.compose(input, output); - assertThat(function.getInputType()).isEqualTo(Foo.class); - assertThat(function.getOutputType()).isEqualTo(String.class); - assertThat(function.getInputWrapper()).isEqualTo(Flux.class); - assertThat(function.getOutputWrapper()).isEqualTo(Flux.class); - assertThat(function.isMessage()).isEqualTo(false); - } - - @Test - public void idempotentMessage() { - FunctionType function = FunctionType.from(Foo.class).to(Bar.class).message() - .wrap(Flux.class); - assertThat(function).isSameAs(function.message()); - } - - @Test - public void idempotentWrapper() { - FunctionType function = FunctionType.from(Foo.class).to(Bar.class).message() - .wrap(Flux.class); - assertThat(function).isSameAs(function.wrap(Flux.class)); - } - - @Test - public void nonWrapper() { - FunctionType function = FunctionType.from(Foo.class).to(Bar.class); - assertThat(function).isSameAs(function.wrap(Object.class)); - } - - private static class SupplierOfRegistrationOfIntegerToString - implements Supplier>> { - - @Override - public FunctionRegistration> get() { - return new FunctionRegistration>( - new IntegerToString(), "ints"); - } - - } - - private static class SupplierOfIntegerToString - implements Supplier> { - - @Override - public Function get() { - return new IntegerToString(); - } - - } - - private static class IntegerToString implements Function { - - @Override - public String apply(Integer t) { - return "" + t; - } - - } - - private static class StringToMap implements Function> { - - @Override - public Map apply(String t) { - return Collections.emptyMap(); - } - - } - - private static class FooToFoo implements Function { - - @Override - public Bar apply(Foo t) { - return new Bar(); - } - - } - - private static class FluxToFlux implements Function, Flux> { - - @Override - public Flux apply(Flux t) { - return t.map(f -> new Bar()); - } - - } - - private static class FluxMessageToFluxMessage - implements Function>, Flux>> { - - @Override - public Flux> apply(Flux> t) { - return t.map(f -> MessageBuilder.withPayload(new Bar()) - .copyHeadersIfAbsent(f.getHeaders()).build()); - } - - } - - private static class Foo { - - } - - private static class Bar { - - } - - private static class MyFunction - implements Function, Flux>, Tuple3, Flux, Flux>> { - - @Override - public Tuple3, Flux, Flux> apply(Tuple2, Flux> t) { - return null; - } - -} - -} diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java deleted file mode 100644 index 66e257ec9..000000000 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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.context; - -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.support.GenericApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * - * @author Oleg Zhurakousky - * - */ -public class SpringFunctionAdapterInitializerTests { - - private AbstractSpringFunctionAdapterInitializer initializer; - - @AfterEach - public void after() { - System.clearProperty("function.name"); - if (this.initializer != null) { - this.initializer.close(); - } - } - - @Test - public void nullSource() { - Assertions.assertThrows(IllegalArgumentException.class, () -> - this.initializer = new AbstractSpringFunctionAdapterInitializer(null) { - - }); - } - - @Test - public void sourceAsMainClassProperty() { - try { - System.setProperty("MAIN_CLASS", FluxFunctionConfig.class.getName()); - this.initializer = new AbstractSpringFunctionAdapterInitializer() { - - }; - } - finally { - System.clearProperty("MAIN_CLASS"); - } - } - - @Test - public void functionBean() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(FluxFunctionConfig.class) { - - }; - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Test - public void functionApp() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(FluxFunctionApp.class) { - - }; - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - Object o = result.blockFirst(); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Test - public void functionCatalog() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(FunctionConfig.class) { - - }; - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Test - @Disabled // related to boot 2.1 no bean override change - public void functionRegistrar() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(FunctionRegistrar.class) { - - }; - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Test - public void namedFunctionCatalog() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(NamedFunctionConfig.class) { - - }; - System.setProperty("function.name", "other"); - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Test - public void consumerCatalog() { - this.initializer = new AbstractSpringFunctionAdapterInitializer(ConsumerConfig.class) { - - }; - this.initializer.initialize(null); - Flux result = Flux.from(this.initializer.apply(Flux.just(new Foo()))); - assertThat(result.toStream().collect(Collectors.toList())).isEmpty(); - } - - @Test - public void supplierCatalog() { - initializer = new AbstractSpringFunctionAdapterInitializer(SupplierConfig.class) { - - }; - initializer.initialize(null); - Flux result = Flux.from(initializer.apply(null)); - assertThat(result.blockFirst()).isInstanceOf(Bar.class); - } - - @Configuration - protected static class FluxFunctionConfig { - - @Bean - public Function, Flux> function() { - return flux -> flux.map(foo -> new Bar()); - } - - } - - protected static class FluxFunctionApp implements Function, Flux> { - - @Override - public Flux apply(Flux flux) { - return flux.map(foo -> new Bar()); - } - - } - - protected static class FunctionRegistrar - implements ApplicationContextInitializer { - - public Function, Flux> function() { - return flux -> flux.map(foo -> new Bar()); - } - - @Override - public void initialize(GenericApplicationContext context) { - context.registerBean("function", FunctionRegistration.class, - () -> new FunctionRegistration, Flux>>( - function()).name("function") - .type(FunctionType.from(Foo.class).to(Bar.class) - .wrap(Flux.class).getType())); - } - - } - - @Configuration - @Import(ContextFunctionCatalogAutoConfiguration.class) - protected static class FunctionConfig { - - @Bean - public Function function() { - return foo -> new Bar(); - } - - } - - @Configuration - @Import(ContextFunctionCatalogAutoConfiguration.class) - protected static class NamedFunctionConfig { - - @Bean - public Function other() { - return foo -> new Bar(); - } - - } - - @Configuration - @Import(ContextFunctionCatalogAutoConfiguration.class) - protected static class SupplierConfig { - @Bean - public Supplier supplier() { - return () -> { - return new Bar(); - }; - } - } - - @Configuration - @Import(ContextFunctionCatalogAutoConfiguration.class) - protected static class ConsumerConfig { - - @Bean - public Consumer consumer() { - return foo -> { - }; - } - - } - - protected static class Foo { - - } - - protected static class Bar { - - } -} 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 91bd06857..d750df4b4 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 @@ -52,7 +52,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.function.json.JsonMapper; import org.springframework.context.ApplicationContext; @@ -498,30 +497,35 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(func).isNull(); FunctionRegistry registry = (FunctionRegistry) catalog; try { - FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "a").type(FunctionType.from(Integer.class).to(String.class)); + FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "a") + .type(FunctionTypeUtils.functionType(Integer.class, String.class)); registry.register(registration); fail(); } - catch (IllegalStateException e) { + catch (IllegalArgumentException e) { // good as we expect it to fail } // try { - FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "b").type(FunctionType.from(String.class).to(Integer.class)); + FunctionRegistration registration = new FunctionRegistration(new MyFunction(), "b") + .type(FunctionTypeUtils.functionType(String.class, Integer.class)); registry.register(registration); fail(); } - catch (IllegalStateException e) { + catch (IllegalArgumentException e) { // good as we expect it to fail } // - FunctionRegistration c = new FunctionRegistration(new MyFunction(), "c").type(FunctionType.from(String.class).to(String.class)); + FunctionRegistration c = new FunctionRegistration(new MyFunction(), "c") + .type(FunctionTypeUtils.functionType(String.class, String.class)); registry.register(c); // - FunctionRegistration d = new FunctionRegistration(new RawFunction(), "d").type(FunctionType.from(Person.class).to(String.class)); + FunctionRegistration d = new FunctionRegistration(new RawFunction(), "d") + .type(FunctionTypeUtils.functionType(Person.class, String.class)); registry.register(d); // - FunctionRegistration e = new FunctionRegistration(new RawFunction(), "e").type(FunctionType.from(Object.class).to(Object.class)); + FunctionRegistration e = new FunctionRegistration(new RawFunction(), "e") + .type(FunctionTypeUtils.functionType(Object.class, Object.class)); registry.register(e); } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java index 6f0285422..1f96e0ab1 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java @@ -31,7 +31,6 @@ import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuple3; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.core.ParameterizedTypeReference; import org.springframework.messaging.Message; @@ -114,29 +113,29 @@ public class FunctionTypeUtilsTests { @Test public void testFunctionTypeByClassDiscovery() { - FunctionType type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(Function.class)); - assertThat(type.getInputType()).isAssignableFrom(Object.class); + Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(Function.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(type))).isAssignableFrom(Object.class); - type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MessageFunction.class)); - assertThat(type.getInputType()).isAssignableFrom(String.class); - assertThat(type.getOutputType()).isAssignableFrom(String.class); + type = FunctionTypeUtils.discoverFunctionTypeFromClass(MessageFunction.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfInputType(type))).isAssignableFrom(String.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfOutputType(type))).isAssignableFrom(String.class); - type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageFunction.class)); - assertThat(type.getInputType()).isAssignableFrom(String.class); - assertThat(type.getOutputType()).isAssignableFrom(String.class); + type = FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageFunction.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfInputType(type))).isAssignableFrom(String.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfOutputType(type))).isAssignableFrom(String.class); - type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MessageConsumer.class)); - assertThat(type.getInputType()).isAssignableFrom(String.class); + type = FunctionTypeUtils.discoverFunctionTypeFromClass(MessageConsumer.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfInputType(type))).isAssignableFrom(String.class); - type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageConsumer.class)); - assertThat(type.getInputType()).isAssignableFrom(String.class); + type = FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageConsumer.class); + assertThat(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfInputType(type))).isAssignableFrom(String.class); } @Test public void testWithComplexHierarchy() { - FunctionType type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(ReactiveFunctionImpl.class)); - assertThat(String.class).isAssignableFrom(type.getInputType()); - assertThat(Integer.class).isAssignableFrom(type.getOutputType()); + Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(ReactiveFunctionImpl.class); + assertThat(String.class).isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfInputType(type))); + assertThat(Integer.class).isAssignableFrom(FunctionTypeUtils.getRawType(FunctionTypeUtils.getComponentTypeOfOutputType(type))); } @Test diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java index 9f5840c50..517b08dfa 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java @@ -40,7 +40,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.HybridFunctionalRegistrationTests.UppercaseFunction; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.function.context.config.JsonMessageConverter; @@ -91,7 +90,7 @@ public class SimpleFunctionRegistryTests { public void testCachingOfFunction() { Echo function = new Echo(); FunctionRegistration registration = new FunctionRegistration<>( - function, "echo").type(FunctionType.of(Echo.class)); + function, "echo").type(Echo.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); @@ -107,7 +106,7 @@ public class SimpleFunctionRegistryTests { public void testNoCachingOfFunction() { Echo function = new Echo(); FunctionRegistration registration = new FunctionRegistration<>( - function, "echo").type(FunctionType.of(Echo.class)); + function, "echo").type(Echo.class); registration.getProperties().put("singleton", "false"); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); @@ -124,7 +123,7 @@ public class SimpleFunctionRegistryTests { public void testSCF640() { Echo function = new Echo(); FunctionRegistration registration = new FunctionRegistration<>( - function, "echo").type(FunctionType.of(Echo.class)); + function, "echo").type(Echo.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); @@ -142,27 +141,27 @@ public class SimpleFunctionRegistryTests { new JacksonMapper(new ObjectMapper())); FunctionRegistration reg1 = new FunctionRegistration<>( - new UpperCase(), "uppercase").type(FunctionType.of(UpperCase.class)); + new UpperCase(), "uppercase").type(UpperCase.class); catalog.register(reg1); // FunctionRegistration reg2 = new FunctionRegistration<>( - new UpperCaseMessage(), "uppercaseMessage").type(FunctionType.of(UpperCaseMessage.class)); + new UpperCaseMessage(), "uppercaseMessage").type(UpperCaseMessage.class); catalog.register(reg2); // FunctionRegistration reg3 = new FunctionRegistration<>( - new StringArrayFunction(), "stringArray").type(FunctionType.of(StringArrayFunction.class)); + new StringArrayFunction(), "stringArray").type(StringArrayFunction.class); catalog.register(reg3); // FunctionRegistration reg4 = new FunctionRegistration<>( - new TypelessFunction(), "typeless").type(FunctionType.of(TypelessFunction.class)); + new TypelessFunction(), "typeless").type(TypelessFunction.class); catalog.register(reg4); // FunctionRegistration reg5 = new FunctionRegistration<>( - new ByteArrayFunction(), "typeless").type(FunctionType.of(ByteArrayFunction.class)); + new ByteArrayFunction(), "typeless").type(ByteArrayFunction.class); catalog.register(reg5); // FunctionRegistration reg6 = new FunctionRegistration<>( - new StringListFunction(), "stringList").type(FunctionType.of(StringListFunction.class)); + new StringListFunction(), "stringList").type(StringListFunction.class); catalog.register(reg6); Message collectionMessage = MessageBuilder.withPayload("[\"ricky\", \"julien\", \"bubbles\"]").build(); @@ -218,7 +217,7 @@ public class SimpleFunctionRegistryTests { UpperCase function = new UpperCase(); FunctionRegistration registration = new FunctionRegistration<>( - function, "foo").type(FunctionType.of(UppercaseFunction.class)); + function, "foo").type(UppercaseFunction.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); @@ -237,7 +236,7 @@ public class SimpleFunctionRegistryTests { TestFunction function = new TestFunction(); FunctionRegistration registration = new FunctionRegistration<>( - function, "foo").type(FunctionType.of(TestFunction.class)); + function, "foo").type(TestFunction.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); @@ -246,7 +245,7 @@ public class SimpleFunctionRegistryTests { FunctionInvocationWrapper lookedUpFunction = catalog.lookup("hello"); assertThat(lookedUpFunction).isNotNull(); // because we only have one and can look it up with any name FunctionRegistration registration2 = new FunctionRegistration<>( - function, "foo2").type(FunctionType.of(TestFunction.class)); + function, "foo2").type(TestFunction.class); catalog.register(registration2); lookedUpFunction = catalog.lookup("hello"); assertThat(lookedUpFunction).isNull(); @@ -257,9 +256,9 @@ public class SimpleFunctionRegistryTests { @Test public void testFunctionComposition() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( - new UpperCase(), "uppercase").type(FunctionType.of(UpperCase.class)); + new UpperCase(), "uppercase").type(UpperCase.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + new Reverse(), "reverse").type(Reverse.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(upperCaseRegistration); @@ -268,23 +267,15 @@ public class SimpleFunctionRegistryTests { Function, Flux> lookedUpFunction = catalog .lookup("uppercase|reverse"); assertThat(lookedUpFunction).isNotNull(); - - Flux flux = lookedUpFunction.apply(Flux.just("star")); - flux.subscribe(v -> { - System.out.println(v); - }); - -// assertThat(lookedUpFunction.apply(Flux.just("star")).blockFirst()) -// .isEqualTo("RATS"); } @Test @Disabled public void testFunctionCompositionImplicit() { FunctionRegistration wordsRegistration = new FunctionRegistration<>( - new Words(), "words").type(FunctionType.of(Words.class)); + new Words(), "words").type(Words.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + new Reverse(), "reverse").type(Reverse.class); FunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(wordsRegistration); @@ -301,9 +292,9 @@ public class SimpleFunctionRegistryTests { @Disabled public void testFunctionCompletelyImplicitComposition() { FunctionRegistration wordsRegistration = new FunctionRegistration<>( - new Words(), "words").type(FunctionType.of(Words.class)); + new Words(), "words").type(Words.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + new Reverse(), "reverse").type(Reverse.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(wordsRegistration); @@ -319,9 +310,9 @@ public class SimpleFunctionRegistryTests { @Test public void testFunctionCompositionExplicit() { FunctionRegistration wordsRegistration = new FunctionRegistration<>( - new Words(), "words").type(FunctionType.of(Words.class)); + new Words(), "words").type(Words.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + new Reverse(), "reverse").type(Reverse.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(wordsRegistration); @@ -337,10 +328,10 @@ public class SimpleFunctionRegistryTests { public void testFunctionCompositionWithMessages() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( new UpperCaseMessage(), "uppercase") - .type(FunctionType.of(UpperCaseMessage.class)); + .type(UpperCaseMessage.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( new ReverseMessage(), "reverse") - .type(FunctionType.of(ReverseMessage.class)); + .type(ReverseMessage.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(upperCaseRegistration); @@ -359,9 +350,9 @@ public class SimpleFunctionRegistryTests { public void testFunctionCompositionMixedMessages() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( new UpperCaseMessage(), "uppercase") - .type(FunctionType.of(UpperCaseMessage.class)); + .type(UpperCaseMessage.class); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + new Reverse(), "reverse").type(Reverse.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(upperCaseRegistration); @@ -379,7 +370,7 @@ public class SimpleFunctionRegistryTests { @Test public void testReactiveFunctionMessages() { FunctionRegistration registration = new FunctionRegistration<>(new ReactiveFunction(), "reactive") - .type(FunctionType.of(ReactiveFunction.class)); + .type(ReactiveFunction.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); @@ -407,6 +398,7 @@ public class SimpleFunctionRegistryTests { assertThat(result).isEqualTo("Jim Lahey"); } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void lookup() { SimpleFunctionRegistry functionRegistry = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, @@ -416,7 +408,7 @@ public class SimpleFunctionRegistryTests { Function userFunction = uppercase(); FunctionRegistration functionRegistration = new FunctionRegistration(userFunction, "uppercase") - .type(FunctionType.from(String.class).to(String.class)); + .type(FunctionTypeUtils.functionType(String.class, String.class)); functionRegistry.register(functionRegistration); function = functionRegistry.lookup("uppercase"); @@ -424,20 +416,21 @@ public class SimpleFunctionRegistryTests { } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void lookupDefaultName() { SimpleFunctionRegistry functionRegistry = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); Function userFunction = uppercase(); FunctionRegistration functionRegistration = new FunctionRegistration(userFunction, "uppercase") - .type(FunctionType.from(String.class).to(String.class)); + .type(FunctionTypeUtils.functionType(String.class, String.class)); functionRegistry.register(functionRegistration); FunctionInvocationWrapper function = functionRegistry.lookup(""); assertThat(function).isNotNull(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void lookupWithCompositionFunctionAndConsumer() { SimpleFunctionRegistry functionRegistry = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, @@ -445,7 +438,7 @@ public class SimpleFunctionRegistryTests { Object userFunction = uppercase(); FunctionRegistration functionRegistration = new FunctionRegistration(userFunction, "uppercase") - .type(FunctionType.from(String.class).to(String.class)); + .type(FunctionTypeUtils.functionType(String.class, String.class)); functionRegistry.register(functionRegistration); userFunction = consumer(); @@ -458,6 +451,7 @@ public class SimpleFunctionRegistryTests { functionWrapper.apply("123"); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void lookupWithReactiveConsumer() { SimpleFunctionRegistry functionRegistry = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, @@ -474,12 +468,11 @@ public class SimpleFunctionRegistryTests { functionWrapper.apply("123"); } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void testHeaderEnricherFunction() { FunctionRegistration registration = new FunctionRegistration<>(new HeaderEnricherFunction(), "headerEnricher") - .type(FunctionType.of(HeaderEnricherFunction.class)); + .type(HeaderEnricherFunction.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); @@ -493,7 +486,7 @@ public class SimpleFunctionRegistryTests { @Test public void testReactiveMonoSupplier() { FunctionRegistration registration = new FunctionRegistration<>(new ReactiveMonoGreeter(), - "greeter").type(FunctionType.of(ReactiveMonoGreeter.class)); + "greeter").type(ReactiveMonoGreeter.class); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java index af23aa560..3b3075a73 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java @@ -39,7 +39,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.scan.TestFunction; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.annotation.Bean; @@ -245,17 +245,18 @@ public class ContextFunctionCatalogInitializerTests { private List list = new ArrayList<>(); + @Override public void initialize(GenericApplicationContext context) { + context.registerBean("function", FunctionRegistration.class, - () -> new FunctionRegistration<>(function()).type( - FunctionType.from(Person.class).to(Person.class).getType())); + () -> new FunctionRegistration<>(function()).type(FunctionTypeUtils.functionType(Person.class, Person.class))); context.registerBean("supplier", FunctionRegistration.class, () -> new FunctionRegistration<>(supplier()) - .type(FunctionType.supplier(String.class).getType())); + .type(FunctionTypeUtils.supplierType(String.class))); context.registerBean("consumer", FunctionRegistration.class, () -> new FunctionRegistration<>(consumer()) - .type(FunctionType.consumer(String.class).getType())); + .type(FunctionTypeUtils.consumerType(String.class))); context.registerBean(SimpleConfiguration.class, () -> this); } @@ -296,8 +297,7 @@ public class ContextFunctionCatalogInitializerTests { @Override public void initialize(GenericApplicationContext context) { context.registerBean("function", FunctionRegistration.class, - () -> new FunctionRegistration<>(function()).type( - FunctionType.from(String.class).to(String.class).getType())); + () -> new FunctionRegistration<>(function()).type(FunctionTypeUtils.functionType(String.class, String.class))); context.registerBean(PropertiesConfiguration.class, () -> this); } @@ -317,8 +317,7 @@ public class ContextFunctionCatalogInitializerTests { @Override public void initialize(GenericApplicationContext context) { context.registerBean("function", FunctionRegistration.class, - () -> new FunctionRegistration<>(function()).type( - FunctionType.from(String.class).to(String.class).getType())); + () -> new FunctionRegistration<>(function()).type(FunctionTypeUtils.functionType(String.class, String.class))); context.registerBean(ValueConfiguration.class, () -> this); } @@ -355,8 +354,7 @@ public class ContextFunctionCatalogInitializerTests { context.registerBean(String.class, () -> value()); context.registerBean("foos", FunctionRegistration.class, () -> new FunctionRegistration<>(foos(context.getBean(String.class))) - .type(FunctionType.from(String.class).to(Foo.class) - .getType())); + .type(FunctionTypeUtils.functionType(String.class, Foo.class))); } @Bean diff --git a/spring-cloud-function-samples/function-functional-sample-aws/src/main/java/example/FunctionConfiguration.java b/spring-cloud-function-samples/function-functional-sample-aws/src/main/java/example/FunctionConfiguration.java index 34ff8b4d4..477300d38 100644 --- a/spring-cloud-function-samples/function-functional-sample-aws/src/main/java/example/FunctionConfiguration.java +++ b/spring-cloud-function-samples/function-functional-sample-aws/src/main/java/example/FunctionConfiguration.java @@ -4,7 +4,7 @@ import java.util.function.Function; import org.springframework.boot.SpringBootConfiguration; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.support.GenericApplicationContext; @@ -24,7 +24,6 @@ public class FunctionConfiguration implements ApplicationContextInitializer function = (str) -> str + str.toUpperCase(); context.registerBean("uppercase", FunctionRegistration.class, - () -> new FunctionRegistration<>(function).type( - FunctionType.from(String.class).to(String.class))); + () -> new FunctionRegistration<>(function).type(FunctionTypeUtils.functionType(String.class, String.class))); } } diff --git a/spring-cloud-function-samples/function-sample-aws-custom/src/main/java/com/example/LambdaApplication.java b/spring-cloud-function-samples/function-sample-aws-custom/src/main/java/com/example/LambdaApplication.java index 730053793..3d6ed1751 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom/src/main/java/com/example/LambdaApplication.java +++ b/spring-cloud-function-samples/function-sample-aws-custom/src/main/java/com/example/LambdaApplication.java @@ -7,8 +7,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.FunctionalSpringApplication; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.support.GenericApplicationContext; @@ -32,7 +32,6 @@ public class LambdaApplication @Override public void initialize(GenericApplicationContext context) { context.registerBean("uppercase", FunctionRegistration.class, - () -> new FunctionRegistration<>(uppercase()).type( - FunctionType.from(String.class).to(String.class))); + () -> new FunctionRegistration<>(uppercase()).type(FunctionTypeUtils.functionType(String.class, String.class))); } } diff --git a/spring-cloud-function-samples/function-sample-supplier-exporter/src/main/java/com/example/demo/DemoApplication.java b/spring-cloud-function-samples/function-sample-supplier-exporter/src/main/java/com/example/demo/DemoApplication.java index d41f097c4..fd7b20ea4 100644 --- a/spring-cloud-function-samples/function-sample-supplier-exporter/src/main/java/com/example/demo/DemoApplication.java +++ b/spring-cloud-function-samples/function-sample-supplier-exporter/src/main/java/com/example/demo/DemoApplication.java @@ -4,8 +4,8 @@ import java.util.function.Function; import org.springframework.boot.SpringBootConfiguration; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.FunctionalSpringApplication; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.support.GenericApplicationContext; @@ -21,7 +21,7 @@ public class DemoApplication public void initialize(GenericApplicationContext context) { context.registerBean("foobar", FunctionRegistration.class, () -> new FunctionRegistration<>(new Foobar()) - .type(FunctionType.from(Foo.class).to(Foo.class))); + .type(FunctionTypeUtils.functionType(Foo.class, Foo.class))); } } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java index 92f112953..9d65de859 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/source/FunctionExporterAutoConfiguration.java @@ -30,7 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.web.source.FunctionExporterAutoConfiguration.SourceActiveCondition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -70,10 +70,11 @@ public class FunctionExporterAutoConfiguration { FunctionRegistration>> registration = new FunctionRegistration<>(supplier); Type rawType = ResolvableType.forClassWithGenerics(Supplier.class, this.props.getSource().getType()).getType(); // FunctionType functionType = FunctionType.supplier(this.props.getSource().getType()).wrap(Flux.class); - FunctionType type = FunctionType.of(rawType); - if (this.props.getSource().isIncludeHeaders()) { -// type = type.message(); - } +// FunctionType type = FunctionType.of(rawType); +// if (this.props.getSource().isIncludeHeaders()) { +//// type = type.message(); +// } + Type type = FunctionTypeUtils.discoverFunctionTypeFromClass(HttpSupplier.class); registration = registration.type(type); return registration; } diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/test/FunctionalExporterTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/test/FunctionalExporterTests.java index a75d0806d..de952b7f4 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/test/FunctionalExporterTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/test/FunctionalExporterTests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.function.test; +import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -29,7 +31,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.test.FunctionalSpringBootTest; import org.springframework.cloud.function.test.FunctionalExporterTests.ApplicationConfiguration; import org.springframework.cloud.function.web.source.SupplierExporter; @@ -38,6 +40,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; +import org.springframework.util.ReflectionUtils; import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -114,8 +117,16 @@ public class FunctionalExporterTests { @Override public void initialize(GenericApplicationContext context) { context.registerBean("uppercase", FunctionRegistration.class, - () -> new FunctionRegistration<>(uppercase()).type( - FunctionType.from(Person.class).to(String.class).message())); + () -> new FunctionRegistration<>(uppercase()) + .type(FunctionTypeUtils.discoverFunctionTypeFromFunctionFactoryMethod(this.getClass(), "uppercase"))); + } + + public static Type discoverFunctionTypeFromFunctionFactoryMethod(Class clazz, String methodName) { + return discoverFunctionTypeFromFunctionFactoryMethod(ReflectionUtils.findMethod(clazz, methodName)); + } + + public static Type discoverFunctionTypeFromFunctionFactoryMethod(Method method) { + return method.getGenericReturnType(); } } diff --git a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java index cc6295ff0..3359a742b 100644 --- a/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java +++ b/spring-cloud-function-web/src/test/java/org/springframework/cloud/function/web/function/FunctionEndpointInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2019 the original author or authors. + * Copyright 2019-2021 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. @@ -27,8 +27,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.function.context.FunctionRegistration; -import org.springframework.cloud.function.context.FunctionType; import org.springframework.cloud.function.context.FunctionalSpringApplication; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.support.GenericApplicationContext; import org.springframework.http.HttpStatus; @@ -139,18 +139,19 @@ public class FunctionEndpointInitializerTests { @Override public void initialize(GenericApplicationContext applicationContext) { + applicationContext.registerBean("uppercase", FunctionRegistration.class, () -> new FunctionRegistration<>(uppercase()) - .type(FunctionType.from(String.class).to(String.class))); + .type(FunctionTypeUtils.functionType(String.class, String.class))); applicationContext.registerBean("reverse", FunctionRegistration.class, () -> new FunctionRegistration<>(reverse()) - .type(FunctionType.from(String.class).to(String.class))); + .type(FunctionTypeUtils.functionType(String.class, String.class))); applicationContext.registerBean("lowercase", FunctionRegistration.class, () -> new FunctionRegistration<>(lowercase()) - .type(FunctionType.from(String.class).to(String.class))); + .type(FunctionTypeUtils.functionType(String.class, String.class))); applicationContext.registerBean("supplier", FunctionRegistration.class, () -> new FunctionRegistration<>(supplier()) - .type(FunctionType.supplier(String.class))); + .type(FunctionTypeUtils.supplierType(String.class))); } } From 47b56ed44ef3a0e701134cd0a1695a5c414310fe Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 10 Nov 2021 16:02:10 +0100 Subject: [PATCH 09/41] Initial removal of dependencies on AbstractSpringFunctionAdapterInitializer --- .../adapter/aws/CustomRuntimeInitializer.java | 3 +- .../AzureSpringBootHttpRequestHandler.java | 142 ------------ .../azure/AzureSpringBootRequestHandler.java | 207 ------------------ .../avro/AvroSchemaServiceManager.java | 1 - .../avro/AvroSchemaServiceManagerImpl.java | 1 + .../src/test/java/example/MapTests.java | 59 ----- 6 files changed, 2 insertions(+), 411 deletions(-) delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootHttpRequestHandler.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java delete mode 100644 spring-cloud-function-samples/function-sample-azure/src/test/java/example/MapTests.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeInitializer.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeInitializer.java index 60e8a48f8..92ba13ca1 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeInitializer.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeInitializer.java @@ -19,7 +19,6 @@ package org.springframework.cloud.function.adapter.aws; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer; import org.springframework.cloud.function.web.source.DestinationResolver; import org.springframework.context.ApplicationContextInitializer; @@ -64,7 +63,7 @@ public class CustomRuntimeInitializer implements ApplicationContextInitializer clazz = Thread.currentThread().getContextClassLoader().loadClass(handler); - if (FunctionInvoker.class.isAssignableFrom(clazz) || AbstractSpringFunctionAdapterInitializer.class.isAssignableFrom(clazz)) { + if (FunctionInvoker.class.isAssignableFrom(clazz)) { return false; } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootHttpRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootHttpRequestHandler.java deleted file mode 100644 index 75ae9b568..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootHttpRequestHandler.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.azure; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.HttpRequestMessage; -import com.microsoft.azure.functions.HttpResponseMessage; -import com.microsoft.azure.functions.HttpResponseMessage.Builder; - -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.GenericMessage; - -/** - * Implementation of HTTP Request Handler for Azure which supports - * HttpRequestMessage and HttpResponseMessage the types required by - * Azure Functions for HTTP-triggered functions. - * - * @param input type - * @author Markus Gulden - * - * @since 2.1 - * @deprecated since 3.2 in favor of {@link FunctionInvoker} - */ -@Deprecated -public class AzureSpringBootHttpRequestHandler extends - AzureSpringBootRequestHandler, HttpResponseMessage> { - - public AzureSpringBootHttpRequestHandler(Class configurationClass) { - super(configurationClass); - } - - public AzureSpringBootHttpRequestHandler() { - super(); - } - - @SuppressWarnings("rawtypes") - @Override - protected Object convertEvent(HttpRequestMessage event) { - - if (event.getBody() != null) { - if (functionAcceptsMessage()) { - return new GenericMessage(event.getBody(), getHeaders(event)); - } - else { - return event.getBody(); - } - } - else { - if (functionAcceptsMessage()) { - return new GenericMessage(Optional.empty(), getHeaders(event)); - } - else { - return Optional.empty(); - } - } - } - - protected boolean functionAcceptsMessage() { - - return ((FunctionInvocationWrapper) function()).isInputTypeMessage(); - } - - private MessageHeaders getHeaders(HttpRequestMessage 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.getUri().getPath()); - } - - if (event.getHttpMethod() != null) { - headers.put("httpMethod", event.getHttpMethod().toString()); - } - - headers.put("request", event.getBody()); - return new MessageHeaders(headers); - } - - @SuppressWarnings("unchecked") - @Override - protected HttpResponseMessage convertOutput(Object input, Object output) { - HttpRequestMessage requestMessage = (HttpRequestMessage) input; - if (functionReturnsMessage(output)) { - Message message = (Message) output; - Builder builder = requestMessage - .createResponseBuilder(com.microsoft.azure.functions.HttpStatus.OK) - .body(message.getPayload()); - for (Map.Entry entry : message.getHeaders().entrySet()) { - if (entry.getValue() != null) { - builder = builder.header(entry.getKey(), entry.getValue().toString()); - } - } - return builder.build(); - } - else { - return requestMessage - .createResponseBuilder(com.microsoft.azure.functions.HttpStatus.OK) - .body(output).build(); - } - } - - @Override - public HttpResponseMessage handleRequest(HttpRequestMessage event, - ExecutionContext context) { - HttpResponseMessage result = super.handleRequest(event, context); - if (result == null) { - result = event - .createResponseBuilder(com.microsoft.azure.functions.HttpStatus.OK) - .build(); - } - return result; - } - - protected boolean functionReturnsMessage(Object output) { - return output instanceof Message; - } -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java deleted file mode 100644 index 295e7b63a..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2017-2021 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.azure; - -import java.util.Collection; -import java.util.function.Function; -import java.util.logging.Logger; - -import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.HttpRequestMessage; -import com.microsoft.azure.functions.OutputBinding; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; -import org.springframework.cloud.function.context.FunctionCatalog; -import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -/** - * @param input type - * @param result type - * @author Soby Chacko - * @author Oleg Zhurakousky - * - * @deprecated since 3.2 in favor of {@link FunctionInvoker} - */ -@Deprecated -public class AzureSpringBootRequestHandler extends AbstractSpringFunctionAdapterInitializer { - - @SuppressWarnings("rawtypes") - private static AzureSpringBootRequestHandler thisInitializer; - - private static FunctionCatalog functionCatalog; - - private final static ExecutionContextDelegate EXECUTION_CTX_DELEGATE = new ExecutionContextDelegate(); - - public AzureSpringBootRequestHandler(Class configurationClass) { - super(configurationClass); - } - - public AzureSpringBootRequestHandler() { - super(); - } - - public O handleRequest(ExecutionContext context) { - return this.handleRequest(null, context); - } - - @Override - public void close() { - thisInitializer = null; - super.close(); - } - - @SuppressWarnings("unchecked") - public O handleRequest(I input, ExecutionContext context) { - EXECUTION_CTX_DELEGATE.targetContext = context; - String name = ""; - try { - if (context != null) { - name = context.getFunctionName(); - context.getLogger().info("Handler processing a request for: " + name); - } - - /* - * We need this "caching" logic to ensure that we don't reinitialize Spring Boot app on each invocation - * since Azure creates a new instance of this handler for each invocation, - * see https://github.com/spring-cloud/spring-cloud-function/issues/425 - */ - if (thisInitializer == null /*|| !thisInitializer.functionName.equals(name)*/) { - initialize(EXECUTION_CTX_DELEGATE); - functionCatalog = this.catalog; - thisInitializer = this; - return (O) thisInitializer.handleRequest(input, context); - } - else { - this.catalog = functionCatalog; - thisInitializer.clear(name); - Publisher events = input == null ? Mono.empty() : extract(convertEvent(input)); - if (events instanceof Flux) { - events = Flux.from(events).map(v -> this.toMessage(v, context)); - } - Publisher output = thisInitializer.apply(events); - O result = result(input, output); - if (context != null) { - context.getLogger().fine("Handler processed a request for: " + name); - } - return result; - } - } - catch (Throwable ex) { - if (context != null) { - context.getLogger().throwing(getClass().getName(), "handle", ex); - } - throw new RuntimeException(ex); - } - } - - public void handleOutput(I input, OutputBinding binding, - ExecutionContext context) { - O result = handleRequest(input, context); - binding.setValue(result); - } - - @Override - protected String doResolveName(Object targetContext) { - return ((ExecutionContext) targetContext).getFunctionName(); - } - - protected Object convertEvent(I input) { - return input; - } - - protected Flux extract(Object input) { - if (!isSingleInput(this.getFunction(), input)) { - return Flux.fromIterable((Iterable) input); - } - return Flux.just(input); - } - - protected boolean isSingleInput(Function function, Object input) { - if (!(input instanceof Collection)) { - return true; - } - if (function != null) { - return Collection.class - .isAssignableFrom(((FunctionInvocationWrapper) function).getRawInputType()); - } - return ((Collection) input).size() <= 1; - } - - protected boolean isSingleOutput(Function function, Object output) { - if (!(output instanceof Collection)) { - return true; - } - if (function != null) { - Class outputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(((FunctionInvocationWrapper) function).getOutputType())); - return Collection.class.isAssignableFrom(outputType); - } - return ((Collection) output).size() <= 1; - } - - @SuppressWarnings("rawtypes") - private Message toMessage(Object value, ExecutionContext context) { - if (value instanceof Message) { - return (Message) value; - } - else { - Object payload = value; - if (value instanceof HttpRequestMessage) { - payload = ((HttpRequestMessage) value).getBody(); - if (payload == null) { - payload = ((HttpRequestMessage) value).getQueryParameters(); - } - } - return MessageBuilder.withPayload(payload) - .setHeader(AbstractSpringFunctionAdapterInitializer.TARGET_EXECUTION_CTX_NAME, context).build(); - } - } - - - private static class ExecutionContextDelegate implements ExecutionContext { - - ExecutionContext targetContext; - - @Override - public Logger getLogger() { - if (targetContext == null || targetContext.getLogger() == null) { - return Logger.getAnonymousLogger(); - } - return targetContext.getLogger(); - } - - @Override - public String getInvocationId() { - return targetContext.getInvocationId(); - } - - @Override - public String getFunctionName() { - return targetContext.getFunctionName(); - } - - @Override - public String toString() { - return "ExecutionContextDelegate over: " + this.targetContext; - } - } -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManager.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManager.java index 9152df071..ed208dc93 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManager.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManager.java @@ -63,7 +63,6 @@ public interface AvroSchemaServiceManager { * @param writerSchema {@link Schema} writerSchema provided at run time * @return datum reader which can be used to read Avro payload */ - @SuppressWarnings({ "unchecked", "rawtypes" }) DatumReader getDatumReader(Class type, Schema schema, Schema writerSchema); /** diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManagerImpl.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManagerImpl.java index 77faaabd9..eee848412 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManagerImpl.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/converter/avro/AvroSchemaServiceManagerImpl.java @@ -71,6 +71,7 @@ public class AvroSchemaServiceManagerImpl implements AvroSchemaServiceManager { * @param schema {@link Schema} of object which needs to be serialized * @return datum writer which can be used to write Avro payload */ + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public DatumWriter getDatumWriter(Class type, Schema schema) { DatumWriter writer; diff --git a/spring-cloud-function-samples/function-sample-azure/src/test/java/example/MapTests.java b/spring-cloud-function-samples/function-sample-azure/src/test/java/example/MapTests.java deleted file mode 100644 index c7f3e893f..000000000 --- a/spring-cloud-function-samples/function-sample-azure/src/test/java/example/MapTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2012-2020 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 example; - -import com.microsoft.azure.functions.ExecutionContext; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import java.util.logging.Logger; -import static org.assertj.core.api.Assertions.assertThat; - -import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler; - -/** - * @author Dave Syer - * - */ -public class MapTests { - - @Test - public void start() throws Exception { - AzureSpringBootRequestHandler handler = new AzureSpringBootRequestHandler<>( - Config.class); - ExecutionContext ec = new ExecutionContext() { - @Override - public Logger getLogger() { - return Logger.getAnonymousLogger(); - } - - @Override - public String getInvocationId() { - return "id1"; - } - - @Override - public String getFunctionName() { - return "uppercase"; - } - }; - - String result = handler.handleRequest("{\"greeting\": \"hello\",\"name\": \"your_name\"}", ec); - handler.close(); - assertThat(result).isEqualTo("{\"greeting\":\"HELLO\",\"name\":\"YOUR_NAME\"}"); - } -} From 34967ec77048e6b0aad2458f58a0ed6ccf2e7c3f Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 11 Nov 2021 17:16:19 +0100 Subject: [PATCH 10/41] GH-726 Enhance MessageRoutingCallback to optionally return enriched Message Resolves #726 --- .../main/asciidoc/spring-cloud-function.adoc | 31 ++-- .../context/MessageRoutingCallback.java | 61 ++++++- .../catalog/SimpleFunctionRegistry.java | 4 +- .../context/config/RoutingFunction.java | 33 ++-- .../context/MessageRoutingCallbackTests.java | 149 ++++++++++++++++++ 5 files changed, 246 insertions(+), 32 deletions(-) create mode 100644 spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/MessageRoutingCallbackTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-function.adoc b/docs/src/main/asciidoc/spring-cloud-function.adoc index 36bd0c7e4..12d7784f5 100644 --- a/docs/src/main/asciidoc/spring-cloud-function.adoc +++ b/docs/src/main/asciidoc/spring-cloud-function.adoc @@ -152,20 +152,13 @@ The `MessageRoutingCallback` is a strategy to assist with determining the name o [source, java] ---- public interface MessageRoutingCallback { - - /** - * Determines the name of the function definition to route incoming {@link Message}. - * - * @param message instance of incoming {@link Message} - * @return the name of the route-to function definition - */ - String functionDefinition(Message message); + FunctionRoutingResult routingResult(Message message); + . . . } ---- -All you need to do is implement it and and register it as a bean. The framework will automatically -pick it up and use it for routing decisions. -For example +All you need to do is implement and register it as a bean to be picked up by the `RoutingFunction`. +For example: [source, java] ---- @@ -173,15 +166,25 @@ For example public MessageRoutingCallback customRouter() { return new MessageRoutingCallback() { @Override - public String functionDefinition(Message message) { - return (String) message.getHeaders().get("func_name"); + FunctionRoutingResult routingResult(Message message) { + return new FunctionRoutingResult((String) message.getHeaders().get("func_name")); } }; } ---- In the preceding example you can see a very simple implementation of `MessageRoutingCallback` which determines the function definition from -`func_name` header of the incoming Message. +`func_name` Message header of the incoming Message and returns the instance of `FunctionRoutingResult` containing the definition of function to invoke. + +Additionally, the `FunctionRoutingResult` provides another constructor allowing you to provide an instance of `Message` as second argument to be used down stream. +This is primarily for runtime optimizations. To better understand this case let's look at the following scenario. +You need to route based on the payoload type. However, an input Message typically comes in as let's say JSON payload (as `byte[]`) . In order +to determine the route-to function definition you need to first process such JSON and potentially create an instance of the target type. +Once that determination is done you can pass it to `RoutingFunction` which still has a reference to the original Message with un-processed payload +This means that somewhere downstream, type conversion/transformation would need to be repeated. + +Allowing you to create a new `Message` with converted payload as part of the `FunctionRoutingResult` will instruct `RoutingFunction` to use such `Message` +downstream. So effectively you letting the framework to benefit from the work you already did. *Message Headers* diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/MessageRoutingCallback.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/MessageRoutingCallback.java index 90eda9b8d..1d7b0ada1 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/MessageRoutingCallback.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/MessageRoutingCallback.java @@ -22,8 +22,7 @@ import org.springframework.messaging.Message; /** * Java-based strategy to assist with determining the name of the route-to function definition. * Once implementation is registered as a bean in application context - * it will be picked up by a {@link RoutingFunction} and used to determine the name of the - * route-to function definition. + * it will be picked up by the {@link RoutingFunction}. * * While {@link RoutingFunction} provides several mechanisms to determine the route-to function definition * this callback takes precedence over all of them. @@ -34,10 +33,58 @@ import org.springframework.messaging.Message; public interface MessageRoutingCallback { /** - * Determines the name of the function definition to route incoming {@link Message}. - * - * @param message instance of incoming {@link Message} - * @return the name of the route-to function definition + * @deprecated in 3.1 in favor of {@link #routingResult(Message)} */ - String functionDefinition(Message message); + @Deprecated + default String functionDefinition(Message message) { + return null; + } + + /** + * Computes and returns the instance of {@link FunctionRoutingResult} which encapsulates, + * at the very minimum, function definition and optionally Message to be used downstream. + *

+ * Providing such message is primarily an optimization feature. It could be useful for cases + * where routing procedure is complex and results in, let's say, conversion of the payload to + * the target type, which would effectively be thrown away if the ability to modify the target + * message for downstream use didn't exist, resulting in repeated transformation, type conversion etc. + * + * @param message input message + * @return instance of {@link FunctionRoutingResult} containing the result of the routing computation + */ + default FunctionRoutingResult routingResult(Message message) { + return new FunctionRoutingResult(functionDefinition(message)); + } + + /** + * Domain object that represents the result of the {@link MessageRoutingCallback#routingResult(Message)} + * computation. It consists of function definition and optional Message to be used downstream + * (see {@link MessageRoutingCallback#routingResult(Message)} for more details. + * + * @author Oleg Zhurakousky + * + */ + final class FunctionRoutingResult { + + private final String functionDefinition; + + private final Message message; + + FunctionRoutingResult(String functionDefinition, Message message) { + this.functionDefinition = functionDefinition; + this.message = message; + } + + public FunctionRoutingResult(String functionDefinition) { + this(functionDefinition, null); + } + + public String getFunctionDefinition() { + return functionDefinition; + } + + public Message getMessage() { + return message; + } + } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index a6e13f4f4..20bb6d23f 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -1083,7 +1083,9 @@ public class SimpleFunctionRegistry implements FunctionRegistry { } private boolean isExtractPayload(Message message, Type type) { - + if (this.isRoutingFunction()) { + return false; + } if (FunctionTypeUtils.isCollectionOfMessage(type)) { return true; } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java index e223a7f2d..a77c7f3ee 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java @@ -27,6 +27,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionProperties; import org.springframework.cloud.function.context.MessageRoutingCallback; +import org.springframework.cloud.function.context.MessageRoutingCallback.FunctionRoutingResult; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.BeanResolver; @@ -104,7 +105,15 @@ public class RoutingFunction implements Function { if (input instanceof Message) { Message message = (Message) input; if (this.routingCallback != null) { - function = this.functionFromCallback(message); + FunctionRoutingResult routingResult = this.routingCallback.routingResult(message); + if (routingResult != null) { + if (StringUtils.hasText(routingResult.getFunctionDefinition())) { + function = this.functionFromDefinition(routingResult.getFunctionDefinition()); + } + if (routingResult.getMessage() != null) { + message = routingResult.getMessage(); + } + } } if (function == null) { if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.definition"))) { @@ -172,15 +181,19 @@ public class RoutingFunction implements Function { + "spring.cloud.function.routing-expression' as application properties."); } - private FunctionInvocationWrapper functionFromCallback(Object input) { - if (input instanceof Message) { - String functionDefinition = this.routingCallback.functionDefinition((Message) input); - if (StringUtils.hasText(functionDefinition)) { - return this.functionFromDefinition(functionDefinition); - } - } - return null; - } +// private FunctionInvocationWrapper functionFromCallback(Object input) { +// if (input instanceof Message) { +// Object routingResult = this.routingCallback.functionDefinition((Message) input); +// if (routingResult != null && routingResult instanceof String) { +// +// } +// if (StringUtils.hasText(functionDefinition)) { +// return this.functionFromDefinition(functionDefinition); +// } +// } +// logger.info("Unable to determine route-to function from the provided MessageRoutingCallback"); +// return null; +// } private FunctionInvocationWrapper functionFromDefinition(String definition) { FunctionInvocationWrapper function = functionCatalog.lookup(definition); diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/MessageRoutingCallbackTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/MessageRoutingCallbackTests.java new file mode 100644 index 000000000..42fc259f3 --- /dev/null +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/MessageRoutingCallbackTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021-2021 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.context; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; +import org.springframework.cloud.function.context.config.RoutingFunction; +import org.springframework.cloud.function.json.JsonMapper; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MessageRoutingCallbackTests { + + private ApplicationContext context; + + @BeforeEach + public void before() { + System.clearProperty("spring.cloud.function.definition"); + } + + @SuppressWarnings("unchecked") + @Test + public void testRoutingCallbackWithMessageModification() { + FunctionCatalog catalog = this.configureCatalog(SamppleConfiguration.class); + SamppleConfiguration conf = context.getBean(SamppleConfiguration.class); + FunctionInvocationWrapper function = (FunctionInvocationWrapper) catalog.lookup(RoutingFunction.FUNCTION_NAME, "application/json"); + String foo = "{\"foo\":\"blah\"}"; + Message fooResult = (Message) function.apply(MessageBuilder.withPayload(foo.getBytes()).build()); + String bar = "{\"bar\":\"blah\"}"; + Message barResult = (Message) function.apply(MessageBuilder.withPayload(bar.getBytes()).build()); + assertThat(fooResult.getPayload()).isEqualTo("\"foo\"".getBytes()); + assertThat(barResult.getPayload()).isEqualTo("\"bar\"".getBytes()); + + assertThat(fooResult.getHeaders().get("originalId")).isEqualTo(conf.createdMessageIds.get("foo")); + assertThat(barResult.getHeaders().get("originalId")).isEqualTo(conf.createdMessageIds.get("bar")); + } + + private FunctionCatalog configureCatalog(Class... configClass) { + this.context = new SpringApplicationBuilder(configClass) + .run("--logging.level.org.springframework.cloud.function=DEBUG", + "--spring.main.lazy-initialization=true"); + FunctionCatalog catalog = context.getBean(FunctionCatalog.class); + return catalog; + } + + @EnableAutoConfiguration + private static class SamppleConfiguration { + + Map createdMessageIds = new HashMap<>(); + + @Bean + public MessageRoutingCallback messageRoutingCallback(JsonMapper jsonMapper) { + return new MessageRoutingCallback() { + + @Override + public FunctionRoutingResult routingResult(Message message) { + String payload = new String((byte[]) message.getPayload()); + + MessageBuilder builder; + String functionDefinition; + if (payload.contains("foo")) { + builder = MessageBuilder.withPayload(jsonMapper.fromJson(payload, Foo.class)); + functionDefinition = "foo"; + } + else { + builder = MessageBuilder.withPayload(jsonMapper.fromJson(payload, Bar.class)); + functionDefinition = "bar"; + } + Message m = builder.copyHeaders(message.getHeaders()).build(); + createdMessageIds.put(functionDefinition, m.getHeaders().getId()); + FunctionRoutingResult functionRoutingResult = new FunctionRoutingResult(functionDefinition, m); + return functionRoutingResult; + } + }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public Function, Message> foo() { + return foo -> { + Message m = MessageBuilder.withPayload("foo").setHeader("originalId", foo.getHeaders().getId()).build(); + createdMessageIds.put("foo", foo.getHeaders().getId()); + return m; + }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public Function, Message> bar() { + return bar -> { + Message m = MessageBuilder.withPayload("bar").setHeader("originalId", bar.getHeaders().getId()).build(); + createdMessageIds.put("bar", bar.getHeaders().getId()); + return m; + }; + } + } + + + public static class Foo { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + } + + public static class Bar { + private String bar; + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + } +} From 8f67562cfc81edda897ad41d6a420d653c9fd33b Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 11 Nov 2021 17:28:14 +0100 Subject: [PATCH 11/41] Remove commented code --- .../function/context/config/RoutingFunction.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java index a77c7f3ee..63445f1af 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java @@ -181,20 +181,6 @@ public class RoutingFunction implements Function { + "spring.cloud.function.routing-expression' as application properties."); } -// private FunctionInvocationWrapper functionFromCallback(Object input) { -// if (input instanceof Message) { -// Object routingResult = this.routingCallback.functionDefinition((Message) input); -// if (routingResult != null && routingResult instanceof String) { -// -// } -// if (StringUtils.hasText(functionDefinition)) { -// return this.functionFromDefinition(functionDefinition); -// } -// } -// logger.info("Unable to determine route-to function from the provided MessageRoutingCallback"); -// return null; -// } - private FunctionInvocationWrapper functionFromDefinition(String definition) { FunctionInvocationWrapper function = functionCatalog.lookup(definition); Assert.notNull(function, "Failed to lookup function to route based on the value of 'spring.cloud.function.definition' property '" From c62b83d824988200805d0ce43343534a56b50cbb Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 12 Nov 2021 11:36:40 +0100 Subject: [PATCH 12/41] GH-725 Fix getNames() method to properly compute available function names Resolves #725 --- .../BeanFactoryAwareFunctionRegistry.java | 2 + .../.jdk8 | 0 .../README.adoc | 7 + .../build.gradle | 93 ++++++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53556 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../gradlew | 164 ++++++++++++++++++ .../gradlew.bat | 90 ++++++++++ .../pom.xml | 150 ++++++++++++++++ .../java/example/FunctionConfiguration.java | 59 +++++++ .../src/main/resources/application.properties | 2 + .../src/main/resources/log4j.properties | 20 +++ .../src/test/java/example/MapTests.java | 31 ++++ 13 files changed, 623 insertions(+) create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/.jdk8 create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/README.adoc create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/build.gradle create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/gradle/wrapper/gradle-wrapper.jar create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/gradle/wrapper/gradle-wrapper.properties create mode 100755 spring-cloud-function-samples/function-sample-functional-aws-routing/gradlew create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/gradlew.bat create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/java/example/FunctionConfiguration.java create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/application.properties create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/log4j.properties create mode 100644 spring-cloud-function-samples/function-sample-functional-aws-routing/src/test/java/example/MapTests.java 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 942ae7387..83f1867dc 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 @@ -89,6 +89,8 @@ public class BeanFactoryAwareFunctionRegistry extends SimpleFunctionRegistry imp .addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(Supplier.class))); registeredNames .addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(Consumer.class))); + registeredNames + .addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(FunctionRegistration.class))); } else { registeredNames.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(type))); diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/.jdk8 b/spring-cloud-function-samples/function-sample-functional-aws-routing/.jdk8 new file mode 100644 index 000000000..e69de29bb diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/README.adoc b/spring-cloud-function-samples/function-sample-functional-aws-routing/README.adoc new file mode 100644 index 000000000..fc0c41a2c --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/README.adoc @@ -0,0 +1,7 @@ +This example demonstrates routing capabilities of spring-cloud-function when deployed as AWS Lambdas. +It is almost identical to the `function-sample-aws-routing` , so most of what's described it its README applies to this example as well. + +The main difference though is that this example uses functional bean definition feature of Spring. + +It also uses `MessageRoutingCallback` in place of `spring_cloud_function_definition` and/or `spring_cloud_function_routingExpression`, effectively giving you more flexibility with your routing decisions. + diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/build.gradle b/spring-cloud-function-samples/function-sample-functional-aws-routing/build.gradle new file mode 100644 index 000000000..a641c315f --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/build.gradle @@ -0,0 +1,93 @@ +buildscript { + ext { + springBootVersion = '2.2.0.BUILD-SNAPSHOT' + wrapperVersion = '1.0.17.RELEASE' + shadowVersion = '5.1.0' + } + repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url "https://repo.spring.io/snapshot" } + maven { url "https://repo.spring.io/milestone" } + } + dependencies { + classpath "com.github.jengelman.gradle.plugins:shadow:${shadowVersion}" + classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${wrapperVersion}") + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("io.spring.gradle:dependency-management-plugin:1.0.8.RELEASE") + } +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'eclipse' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'org.springframework.boot' +apply plugin: 'org.springframework.boot.experimental.thin-launcher' +apply plugin: 'io.spring.dependency-management' + +group = 'io.spring.sample' +version = '2.0.0.RELEASE' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://repo.spring.io/snapshot" } + maven { url "https://repo.spring.io/milestone" } +} + +ext { + springCloudFunctionVersion = "3.0.0.BUILD-SNAPSHOT" + awsLambdaEventsVersion = "2.0.2" + awsLambdaCoreVersion = "1.1.0" +} +ext['reactor.version'] = "3.1.7.RELEASE" + +assemble.dependsOn = [shadowJar, thinJar] + +jar { + manifest { + attributes 'Main-Class': 'example.Config' + } +} + +import com.github.jengelman.gradle.plugins.shadow.transformers.* + +shadowJar { + classifier = 'aws' + dependencies { + exclude( + dependency("org.springframework.cloud:spring-cloud-function-web:${springCloudFunctionVersion}")) + } + // Required for Spring + mergeServiceFiles() + append 'META-INF/spring.handlers' + append 'META-INF/spring.schemas' + append 'META-INF/spring.tooling' + transform(PropertiesFileTransformer) { + paths = ['META-INF/spring.factories'] + mergeStrategy = "append" + } +} + +configurations { + testCompile.extendsFrom(compileOnly) +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-function-dependencies:${springCloudFunctionVersion}" + } +} + +dependencies { + compile("org.springframework.cloud:spring-cloud-function-adapter-aws") + compile("org.springframework.cloud:spring-cloud-starter-function-webflux") + compile("org.springframework.boot:spring-boot-configuration-processor") + compileOnly("com.amazonaws:aws-lambda-java-events:${awsLambdaEventsVersion}") + compileOnly("com.amazonaws:aws-lambda-java-core:${awsLambdaCoreVersion}") + testCompile('org.springframework.boot:spring-boot-starter-test') +} diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/gradle/wrapper/gradle-wrapper.jar b/spring-cloud-function-samples/function-sample-functional-aws-routing/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e GIT binary patch literal 53556 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�H$uJM z5b&uBBCA_wdL?^xw19P_F!l$XIUCIG0(Uznb36A^l7CS!0R}%?tUXwj0HwXsK4>8v zWE@fGYQ(q1F-!wr2v#*y7wWza-i5khqjQYc`6WHxhz85!iY%{Wb*z~zziBKpL+~P= z5yWtFJwj0m!TPZcI??gVUnnQOG_s*FMi>bxB)n3@mOYG~$F8 zl_Xm}#nH#t1z6WP61iq!0zB{Jh{o+KuI9xVM*x|TC7COi#tnUn_I;MA4`P!sk}}W2 z$gGS}m_|3n{2>Nib`R}0pU=AR9)Uh6;G*?1T2ZSB5`4PjrO>Bt2=i6u=qr=bN)Jho zMV?Wtn1yFbC*Io^`FFE6o|ePN6GG{zD$mtIc0OSsefFkNdF;nI-VNeuPS?6%IPVoN zZsFOKggP&tnTdglp;!r1nb~ME!H<>dW?N62A>Q1QI7WDZr;ehh?{L3L=pIMlpL9<- zCZ-fg1i?An;l=twL*C@`7quCoH<3MF6KapUt`yRJpF@_5T*SKkjpGkuc&h|H=`ud? z`ZbMU&m4ld%TU}+A+8V~1;8C{f84t#jj{05Rv(nfKmS(5<=Ac8!Twv+zNQ2KAo$N0 ztE8Q?i=mCpKTj(+=3sG#PuZ69xtt)EQ_E$H(y>G9(Tc1>K{$_6M z*(L~w^!?vvr`|bde{$}8^!2_!m&7A22>lTX_-4~b$zzFP^|OM2SO6_YC(5x3nDFZF zLEs;<=Rhe2kWFopSdxKt#+6GlvG$4b&}%<@1KN1(I;X?0JG+# zOZ+SI(Rz6pJnLxoojp_o=1!h~JgSvFTm#aA(MK;!EfdNVDQXa* z&OSYBpIIn<0tfRSotyL5B*mozW{+MLZ6NMLdlU~=0cuYk{B}v^W)@XIJ)rGX--$xE zOcvV!YR_%}tq!75cM%KJ4z>o<-#?T-I%Kk_LSFz{9lHk$0c_9Q_`|<#-aCblZ)o=E z*hH(RzI&AO5E03$9B2e^8%VO=Ic`s>OC%|BVCLoQQbv;^DMQ^Uw~-6%GO^F}H0Q~q z^f33U->p7+w08Mu`8u@@tTTdOW34aQ*zLPo3M*ZgM$1;R*;#AtJ6(i#%35VYXVR~_ zpR*$Hu4*h>k<4nGL6_ctd(c>3Fj`0BNeVt%XZj?1n3pFSWG&#xyR5p9Jv$6nTu7ep z?1&YWZQu<{`E%?dM-RU+EZMY2%EDea9xT>s>$*;qAlk-5oOIejvmMX=Dq4!!RUk=a zamTctj!;C0!kjqf;w{^1TIo=<;5h(Fc&cSFE^CdtNLq|vxH@9x>|8h1&ggl0X!ym_ zxDkU%TWQgqxL#tcz=HsPkx1(`m~!V*zIMr!EW@nJ8EsF5D1i?_3bVt6HC-~|(pC+o zolB0hY3Npl)MYwqOg)KHp8bH;7}-IT!ab|vHd#`jh;fZ<<}KC7PEI6)jPuAiRJGC5 z2&o+9RNmrt5uHY7Ei0NyCNA<4mLnKiFYNv_Zb#Nii3WTZ0arZ8AT4M0>{%QkfFKHD z$$+eh87@<>*<{1qeS%#EY7=9pnWpm2e2)YsTnSN=OZ;bh@jzvAJ7{9b^qHwKQXd&- z%P@H^nn=iub17MjB9)=GFUvK6%wfa84NFp5%?$!9s);AdXonKo1(r8TF-+CxrZNsr z&~Nv31)}ejFF>%}r3{F{mBb*6PpWF=m1;g?!&1Yw@g9xX(CztT)5@3!PJ$MraL?jJ zjIfepZ3R}0DTSdM7v5{g4CqqENzH&qX~|~OOAZ?k(03=3VqR=omosOJO0#<^kry}S zMOVziT*;@o#igZ%dH=|V33S4P3X#diBc9o-J2t^IYq9m{K7GEtHmM_yBtV6$dz7+GSDI~g-K~b{o`Ud#% za0>r2$Osa6KCfwq^?pc*f*-YeG33x$$Cz>r@k4A{>e&zlHn~AYPNFAkSGe@|SF%2qflcY{3Q}TP1xU;;lixI`{PI_{1MwPU# zb8@!|+^PX>d@Px~2o3tYZS<^mg8`s&^A%j$#_ecM)T0-=M6*JcsBjG$6!qH-)6k^r z=hP|(rciXq{A45YWNjc*3tE28s-&}Y*eX(?Dl3}SRu~$6>Iiz?;9=wGO3&_yuud9e zI;ydoyIqTk1TB7ZTT{o1+!@^A%5#rZX4&G?bC6Vjp}Q)V%s16{j$h#-0dMi5>oaC* zU7@wAR|uZ!g;*b6%$SP9WYJtzOSYZDh1c(z!EV*QKzo%BvfbkQv*RPPRQm&M)gPX{ zsGE;rsTtrJ$#Y-96Z*&W0@1o8i1XD}SJet-l%J+a?+-Q*x7&~$2T(*W!GkT;zTp0% zNA(Z6)VBxSak^X6;6eB5FV>%~$+vsI)VmXV3FrLDw`e5ziZ6n180=s3hq09zred)+ zgJxaVKHB88?P~L<=_F^?2OWvaMvl_Lf>sx1GE2t38EFH4*y%WGwX9|A`ZH11xDv-% z3(>w@i{-S_vscw(nT*5!zMm)OY9HA?0x+)$lY58XGTd?$B3bT8G>2Nx$&v++LtnP3 zw}ctz1peYD;s&U(-^Myl#2TRgMq>XF?%dT=NcS~K*x?!t!7>qNE z#XC*r*1Tmas=7$c($69)&0Q|gv4u14v;$|>JCPh{TE18`JLEk$4XUNT)N=8{H?x*& zvob>*k&1|Mkkd%B@&YU_Lcn6yuNS9U<3xC>F0xW3NJsSKU{z_OEIUWa!kVhos3p^e znKBiVqZGn&Zfiz_FCObw-B89YT-{>XtOQQPL1W`9eIoGH-yu`;QO593{jOJqGn?rW z=RZk&t9S(Xl|LZ(OCOgW*&y;4vV)EVx-q4}3kS|HZRW|V9K(LmDf^v;cNIA<6Xu;r zr&oQ^+#ynltMZM`QGV&B_LCdX;Ne^G^-p>$C`a&0*)GRI%e-E{tr+g{@f;iM4wUfPv7pnd_ccS(@ z4{d>u?2E(%@tJmuYw(j8bKAF*cbJo=l*&?B*~c9JD0L7D9LGrhr;Cdt zncS<5VKKJXK?NvGezTQjVUEao!!?}QQz%e#pJ`pN*=dEnReH3bA86g#Q&aLzn9ReZ zzJ$1Y2xzkQdOGVMvC7*9JIRk=IPkJQ2Q3hL%S@dl8N9sAYwsaPHJ_V#Ur9yFWa?cX zjz$+PT{j#E`o?A)2J@8F_`LjHqe`B}I=iKBH6G%zkONe{6sF|Z1v_YQ5&iJov>WGX zipwqW?lIMTBKC>nGA2tsNMx`5CdJY5t}Sz&K$ILDLDC^Pxs_SN&B&jwR}-G3CYZ?b zgKQIgD&Y5pU|OO#CgM zDGuh11j==SAiOZK7m6XE5XW7K(-=sL% zH&+Fz#zLnR(xemV8{F6vc-V`jR7;uVCP}E6Ih=qbmD+TbZ0%-$&Jvj$24?|h9`H!y zP_Tq~oX$EP6%+(9dat$vf8(7vrhU`tFbifgmbiJH(c??;^VknrH z0hsB`p0zIK60yzL%uq8HIxikY-MQKue-X0Bb=6c(wEk*{u0TF8t-_|Q3?O!7wDN;z z>J}_l#!p35Wa#!8&${i&4N1dhNxC7AoA!|VwT*p2*5ZBdic8_~ zkfY8g0D2OPVnL0=o~egN@WK#FU(X>U<#}TGn5vFj1{rPxmoMy%^)Wv?A{ASoTusuuqHD7a5BYf}yH8T5&ox(ckKBEO7Rd?Y?Lp&5oNE!c_F zq_zlC1$F{`-KoyC!}LT)RKJ8?u*ioiyHCbjkW@hWoNawAxb?(^dk1pHOkmE}1>J0> zG}DEB*XNnF=GEwAtr6@@RUF?=NFRWh9Yu~`=$C7-iLKM&68Z7$lSa2Q*@8# zr=^)HLw~**-4mMU9p_K_q(NUfgw!mT!&mU6UzRR3?O6+Kf?Bml+DG)4;NHTg#V->s zyl2!8bbaR#xq4a%wC5$AyIvN$3K^|=d2<_Bszp}&D?5ICjvp_Di}EDG=9VygTzAmMB#^O zss~=SJf03Zqu>_Z_sevE`Gw-k0H0vQK&)s_8m#@KSCn1IhS-8236Qy3u!>h&Myz`1Kd8B~HlYtAU=gA11kqTr1`MN9eyqp7elU7>IHRBL9eHY4UWJ;U)t{yN*Rm)~+ss$M3* zIi`3)<{@3Z1heF9@JR!C+xWC##A~Hh6;Jo%oqCK$fPG6;Q%&iwSVez+S&H&4Q3Lap zUzp_C?Bd3k@N0J(XK%I*Y8R~CI>_d(Na+h|_@M&n3!V+t$ONDV-MniLcA-)o=n`-A z<8ttu7TbY&f9C8tiFVKgy;}5p4$ktRr@!JYKa+g+S!26-yZ6r1b6BM82c`o(|AP?0 zWsdI&53A&;EqYJ|$mNdP4zuWK+h<-`H>2EvRYzSDeze~owhCzF^0Iu^xV^Sv!nqE-4@O&@C z!xw^61W&#Ioa2BSBx>;v{M8g!r2;OpS_^Wo%k?M z1ce90s~<)S-q0se_|)Ik!#!_j=fCxaOQcL`BqD`8@WsGWMqEx#v)r zTb_n1GZNvTYT}r9Ag$(i!8X6 zNU$YbD2sh6*}S%!#>qseXVzSBf>J|g&tP1*6;F(7o@z5yBV>-A-B7jDD$%}mKu=Sk zf%YTL_D!P3ujNo-A&!SXL@>`t8oeE<)7Iexa;)be(pOWnJo`y_%5?g?Bb{Z}ptE2I}2DbF^CCr)96 zZd?xW*TqH)B}#ln^QHMl0vFi9DB#20TVb)V^Qgcn0)Pn5QtC|S*aXu1d0YZVxclWn zla0V*_UL8ZB}?}GpxUEvE}5UU{g&yp2-u3POD?+vzbH_ZIN zRg;d~&1^c-`zGviyarVb*dbjO!waqeW4;Cq;S+k3wYM35$?xwUuWHYeBT!~ui^?u2 zDTZnl*=D}kWhrQysw44&$Nj-HI2T1J7ejOO7yPtWc&(=}{Xst2-Xpm5Hw^?R(nORl zSOwG`MxuD_>usNDbhm*wP?Gs$a<)_xk^J>MS8yA#9>Iynllll{WARg{G;EHXW5~Rm zL-|Z^83y%jy-5Zok}|{6-5&6+f3dejs1#g2J()gyET`p4#!=Gv&R=kKKGLVG{l$(k zuBnqP2gKL?<)D89(n(*PI=2Aj@{|2D7901rk8$xu|E<3{jctG{$?BJZ`OP_jqll%=o>SRg|iFp>7h4N6Qe#g*&gbN`CDKxlneuB#GKMN82a|&*-r|8(MUx|XCNs?v_@JrwJ}g0 z1b>lmV2^)q7zrPHc~=+}f7ci!e^K~w(iTHcLQ(?qQO+vdSOVfHybl9#9F<`NjAfiL zpzfSzYhGQp%_aHC$W(cOU0HnZBS5*)rKKjoVXk#yv8|-c70uVW{NZaZa+h72-E7fR zVcaym*Yi3l2bwmQgK^|i|uC9JmO6AKTOo5vSaE7!I z7ZHBuWomktl`=e+6bx-^L31&#i>t|oUVeMQkI}O>)vi3Otn+MRh-9msb!l8`zjS>e zMnz@@b3)gQ)5J>%)w9Zk?$$!iRb}du99&z~D;Ki_0S#o?vL)fjY*wm?^GxM${*Gun zIEbK*(gVC5#6>583s9<3>=)c3k{hbUdh)$UU|bAPFuY&}(krSDl(Zn43%S=hmgshs z=rhpKIIsC!BgObZ!2HuPa&6Q#rAL%7pzPV<=a#n$B&0YL-_V(;Nhr&F=vu37+#xim z{vkE!+&$}q(@;FxP`p?e9ZC z4vpX_#JUbq>_JIgbvIfvrRMIGnav%=hkdOyHPk2j&C_|64`1BE^$=?XOI`Or;6f`i z%+&w0(j-K^MUP-Qc|Xl$J1UgL%$O@>;R1MDR;90qh}(>`OjQIL#PO^Ud7^a} zKEP||e^%jto&@%3V@I!Aq8DlAuW`A;?t{==&x;q%Ah_q{ix0630P2@y;*klP4#WSD zaYvrc6eb!k*X9f+Blw4B+{c_A%nYIP2d0RBGh&eqBaZ_z#;*Yt=}#OjhOqCy=#yQI zhLnTKKJa9b`vB$(Ao&k6%Y3HIpu=gwm5)Ip7dYg$+zm3+8Nuv4&&&(s1N6d8d!kDL zlIe#s9t-S|d?E&24++OCMt$N4hjc`}+dEZx>O6oyo_|611-z}D z72Qwu`{x!>AM|UH_ypY=KYux@1-d~&Lm`*!P$2dQUO7(kmUGD(27|Z}pD-<%rw|?YSLpf58810bgRZon-0n3jtyb004^rTxa-a zKd7jOsj=&SJqSxx_cXv!#rz}NG-1cK6k?auMoCFSYP&ciI<=EVEUAn&zGAbORkS*B z%c8k{9kQ{32LVMvK~;o9gd!qZ+b(zk77BjX0nkOz|t%ZyQwv6Ar9!-%hi0EWRDop&s8J{t(y0 z909e1K0*rT`AAn#<;Vb(bB}h&+k}H;$ou5^)5N2{!G|CKe)3JY>CrILmm~o5W0!tN z9QZxM2S4Fvh-nIpfqDROrU(*+G56EtRg<3&eRzWdV<7qQ+Xp}&Vm}(thcbX3{5}<+k7`Q(^&cHM; zpl;S8UR>zsRN-u#ZSFLxXXd&w^ZzvKkH|Sx|QW;}y zwwjPUwZ>^iUL(>(T;Vp?Oug3rW|qX_4^=p`p$h~p-0jjdiZAZ8#u6qq`J`B(vzM0q zNULLZBad0hD+w7&%@y->WE`Y&H2F)MZLeV;-OxonwCUHW9SFHb;wf~iO&b;(Y@u? z4%$Tw*5v5}98V zAZ>y~BgD&16*=U&=dz6A*+(*dzh4#d=V|EhLBCRaXjJAGzl4-l>$eh+yQQ<~dAmqa zl9#Dzi85)r)=V+bZkEbESsx^rK}j9w%QKNhO3EVOuo4|as4O`0gg{%5M33={#iFwY zV;t7oFqNM>lkPhc4SLqt@NKudj9#nk@;Mm_B2%2BatkFH9*8KcQl|t{KtSjgY z*dyH1Y4R-;uFe>yuk6y09p9}tk*IiQ^&8^Sb@1RwZbDM_s%t=P>0%2-4+(#p&v01E za#7~6OOU}-)7YC^v^1Zg8OOp&zdawbSLKP_iyYi*wnEqBrE)tmr5bIJ9x3%`j7r}x zrGnd+LZ!r@`U&7y(%e?A*VWQee<0^6K6LGn9LX2e#T!d7ldXD>cKA|dyXwhakc>^Y zU|}vjw2zC)R^_3#xlE0`peQcn#`>Y_{xiPi0P;tf?S~YbRn&_m@tTckq9Zo#x#_-- zXdr7e1=gl};Kd#_?fo}C;+H;8`Jv}5%78(8)LH9o3C7p&40<_JO;wcAkjx!LfDGk8DQwau;V^g~l&8@j40GToR?g^-kw zg`U~VD4<;(?gO>o8QOw*o2eOY%b-hogBy+^-P~}9oIk8=OqN)mPV%ErQIVr$u9Zim zPWVp?=}kFPByX$Q9>3O3){Eu(Mmz!xX_{dUCp)ZOqg4dAitL=*7skIWF`qgcKR`=| z73~K%jpmF&%RNio5*}ZrrMQ@dS9P9qEzVREVS!Mjv5?wQ z$NUT#V;GsVUyHZuVn+B#;-QoqrCZjcW86wvJ2!mql*$(h9N|>;flzX+%cPISgz!D)|S2qu8H6sywRqb zH0|YusE-pxerVLq91EJ(4y$S#*5sVlS{7Q1Vm^3dsVzb!C&%owKGo#j+`M5C)`bgSG;KJ7N}V}!HM{-L%%=~hF|}OP z4B=oEPu$ARBWjggMLMW@qnJ2F=a@E5j$x(taAwVba*-i(rC~K~U~CT&AZ^_$pKLC_ zcrJm`yAp)aa#0pU5qG|83u#T|UXiQLGw56RvP9?Plv-;wZG0inQw`1tRbIDlZMG=$ zS|gNO>O<1ZoG2U9Lc!4dAc0qg5MG))j%e(Yjl)iQ)Ae*@?MLAFvMW%2jj zZ2vR`>O-0iRM!3s%B4PpaPN0j&1YI~KjGefFmdX8yi?5`G;JSPJLX19CW%R>L$-2l zg0ubJ)Vj=k4Sqv6*<&4k)JnT|?F343%AoH?&=Y+|^>*VWRx+B?3toG)Nif@!Q1Iad zAo=-XKjdoIpdAq?5jDKyD4h?#;w42Jw}jb;b*m9wl&veNO;Nd&u%acq5R)&6OCxD! zcTzK&>e)#3gsx=jR&3DNKxMOeUipkG=-Fjo@&fs9jJ;EIW!=8+orlHDoo3JJSd@`y+1I$tN#2dj6pE~%ELv|P#LU> zoiF2g3Sa$N)aTgCV{So-dAT@qt|W;9pT34JdcC5%fP$a_bA0s+=%|1Bqa8i?P%GQFXn@ny5sv z$hoFJZ8|eCPH#@tHZK+Tk_}5%!xkj!5;*zf_RumpDb~VeFVHCD+&r(RPP=$s%-meK zfpkJYx{;+d6gVYZPvz&>>KD{MD&A_eUz; z-J>?U)P~OOTL_uhm5ERMn+V;@p2SyC3*99lwtX+3|X>OZn3?WV`e1N zXMW#8K>SF|`4Jx?KQ_Q1E%qsv(Z^0Ie7$A+R*LA{#tw0PH|hO)PDff)ym7Y`Z*&E^ zDZ+Yc_Mo2gbbJf_&bLba=M&AU<83pI@xe zAfIp-=gbZ;@$sWxHKEQuk7E3cXJ^T7d}w9M9Z>>&r;O?BDyV5{s3_nYDCrkn+umNA zOZiEk0Wn2Ny@?YgUS$IccYX#1?rn3#Sd`=nY;)0h7|LD6 z4JU?z?sUhmpzmdYC~N~f`AmT&Mf)%bA!>^fQlb9wjItGcQk(q_d~vMLb==xB60|tB zEF;4Y&$XPOOxnP^N)nQpni)u`BLp{Cu{|h{TG373ctzG70Szai zdfAf((wJP2MV02XykIG=+?}sw7xYe%t{B6UaVTXMqI!xa^+=NHM?&0k*l~#_s6E4Q ze)jCi&R!#Bp-eV%!Th|L=U_jRTp9|PyePmbxDD~5)DLo3j)xuNDrB1@@7j4;1@$KI z^*3w#-=Vm@(fLKcGAtIFAS|eawsoXFid<^@6CwsQmC@&vsL}E_w*8+L5W71w3t^A!F zl?Lt|G9LC=8i4Gwb@DA@+6j_Ik?3s1w|^#r>AzP&-KkbuNJijd=jchdM4=1O>X)08 zKux(&W|)oV8+Rz6@XMlw3dvGNmfk3{DF$t5h*cZ3eq{q4TKgu1J`^u!)RrnAr7jXi zE+v{qGR{^f0gk4a7baDwfg;VSNLGH@$aO{Y&X>RdrQ|@vZEB2Igd-?QyEG`O^kZ8w zy)4Ycu&uY5osWQ{YPMF;Es_aEC@wWyCVHVEufUY#pd8om7#d$T)hG`-V-tnXBFJ*( zn^lHck;P1$k=Wq;AZ(qI6ugCD5*jA_21gs!uFjz*zZM<6srgenF)rCbeo%1*xT?fZ z2vyO1MWI!`SmoTHmLg4U81JUm*YJ%Y@;xzaF~{IC_pSR0M6DLd?BB4>FuvCtXo10OHYn7xB7?}dW9r^o3f0noO8z zF>xgry-GF@6OL`HwL930GNbNg_h<-BW7jz&8XTs|i)sx%VBH-Q#88$Icy+pX!RTK9 zcxw^A8AC{E;u3X*UM@Xm%5Zh}4W*!o2PTvgPls}qtCt*d^J&#!4AO+hLPy4-JZ;0} z)T!r7-3@^#<{=_gkS+&>QH>fC5Rq5jOx0K0-*8oJmN=xdepoqZA&PgVvptyZc<;W0 zX95C&fYzzwnx0%i22m7!auQA+@Zw=&)|kCx@Jg1AVo43 zIOTE=Td=~Y&Lg0d{(~LNCgF0hE^b-V8o3hgviLq-lg|e#AySvbG7Ir|PvIiGjR{X+ zv?YZl{&p>S#N{aQt$fC97*TabZKq+3|BUl zBFl@DF+;NCYxCAoK=CVxf{-T@@t@oJ~7q;_6QAcfWv6uFimU(pZO(^ zF-0ufSPgBLiQYW+*)U8s`<-|_N|@r9^hVDn@C2FKoQ+7sxSc7#yoFr0U# z{|=&N0M`8FhB)*yhb_{b-T^_m=Syi-sgDEWO zE3~Y^lESRO&!w-e?yzhJP2^EcEXmhm{^vN{o^&=(9mlO_jB{NS8<_S?B+k`|W5b8tCkk`ik! zP~h89#WaF*P$$MsOLBLn(4~TKt}W=VgxtUi9R(u{^I_s56?k)T2=0@3{ANXIJhj$1 zsop=_rnp7pnDsO_%p48jW7TsnZtN62+zodXtB-J_dq?mQYM3?SYMfCnZ&t9ZQ2iD< z%s+p%U9>l>s+z3c{<^B~NU2WnysqvAu(B6BSm2}-)mhB=P@bmuALR|h=r}|(Yk_Ld zuX-YtlQG&CU87jzYOT)lgk64hU*=LzTZYkbSx#1!+t#_VtPf!J*XxIbz7!^VP2&!f z$*=J6Lo)4DABzQsAIElQO5W@6#@P3G({;4-Pa$L6xcRq3uFsoqFWi7jS^IF~k-0Lu zxVf?^CFn-|oMv@(tH~H%C1qN^JXBO)Si|rLX%Faj^15i~>OA2)9`zw>p6#0-vw38w z%^KUDx&}Vh7|lSweto0PKO&?3qAF9EBr}9l>_qB=Tbxp(zu3ZPNJ$)AB=eC5uVL^5cMRB{MgKHK|1?ka5N82HCX*|`5o0^Kr*!6s(rJl$ zUi9}JvbAXx_uNlBK;!3`uKyRw>7UW_|3ai?sav_>E};Wga5TetCGoy|Q49fRB%)cB zf`|DgC-jxaUyzAdZf{stdw8BGh9z53oRlIDDYvtqbQZKI)r}C@TpCxalCuyY##ms z9Br^GU+*Occnm#%zBrDsIt_h!DmCg5lM{?WO}oZmK1#GmU=Uf>J0>3pfW??`@d;jn zQ+MxF&^~MjP;FocZ4pzt5>BK;j9D=SU_v)HS4;U`<7O~6pjxceCb_})9L$|h4?(&( zeC{8N-OG%~Kd~r-7HX~cdB>EC*?_3#-Eqh7hzH)|UkJf;3=op9PI;r0b!x>)zA z;p5gSir0i{+gC)(u2$}|Z&nu|G0ds^P~tNfwe%-N1+A&pUu2%1K6B~K-NJQ_d;V$_ zcb1uGMXEV<$G1CiS02>P_rkrV4Dx~n9G^cImHGw$V9}~FbZ(d9eJ2labLk9G=H42C zLU~ggxxVqjC)`8g{u8=@;$65e|Lg=#c%F(PU~+M6z^K1o%pfO$OTPFkdI5+%DQ2%W zLcxjI_rv)O{Wz@+Y+6_?kEr=uFZXuQZppLE$nmq#$oAl&KW)1a6+wb*6q|}hgE0z> zqwhGL1zL5tJzl_+XYpE6b!@0lDs7aK-ddFRex=`|#E@Oi?NT-ES?$rLr>qLlj234~2cbg)dCFsEaUxhCoE zww0TaG%V5#wg_G`j+??MojaIy<4@DgatbDG@`VVOOyd4xC4jX{iP@I_$JlVdg=)*2 z(wel+EVi;yhs+uJ)R}`lfn&}0E!WdnC@b9hYfv8jKcP`aN9|S#2ut9dNuaAKa=6ZAS4Z`GuXW zT8W2UBIBT)zI;ivj1_UmSc%Dey)IGhVLhSUhYTD3Sk_cC$;-$9Ev5Te;LeN%zbX0{nOfuo7z*QMb^k3f#%fd`zl&1JA5gzOCnxado&-u%_+4DYBck!@s#A< zk+9k$Z`H@otY;3_U7CjqPDmA~Z6qs)ly>|;OVFp%{n65d)dIb~SkElpuf-SpHMw6e zfRe=kPA9%ALxxC(v9t~*XxUb!Lq#RoT>@WK&Pvx^JwpqFPCo-A0CN7ZYHQ37Hcvz> zEbopS-zUWaMV8I(1m7npodZ2Z^lX5#$)>j_3`s}@$kC<(LFp>tphVF-2BKU@1qTUrnmoVYOjUiM)UZ^ozdL6Q8~hHW%PC5LhQ zBs_;iO|!EG^~HCyoJRKM&WNq_0+}5r?P?I8Zapm0&tmRc8s87)<#tP-$ZJZ(a@d1V zrTi`?sO#+ER&s94`aX7NxxV=uEvpK(0D_lnSq}^(YQNYr>R8_F_`!a@RU|5gP0jRU zlO>{4Qc=(jk!(>lSwNA8v0Hi5I3235_G;YA2U$n9lFR+kRXFd6HXAm@kA^(kvGZ@4 z$ZPDaAfmj`$ohP}c&48ls=w+4-QE0RE{3%vMb^UvI6CT+zQU?DjNh@cSKjCB-U=vx zH|Mqg4CH<{#JV(T!4M|g+Tr^ok zq9qm#qcJfxqQ!U#jEYP)A}z3OBrq_kM8B8yo)I~w%=|<8WUZ*(zvHPdBjN5%vDyX0 z-v)NE6UL{$M)!O^9^(HI0JZrqBhC!68-dhYu_v9*z0&A$uGwbqSy6J*~BQg z7L03dlL1HDWS`Pr^}s=9I3E^bL^ZP)jG8|PDdLFKa3+wNpkLg?TV{Afm399sb^47Y zI?}$f;mZOnf#RpzrpB71eCy#YID~miHph#Te>sBYtvRHA(;8Vr{hS^?_3R0#EYnRFnTZ;&44bWTgAcK-dcy~?t$qUrAwTw<7ryWu7g=J$OS(UT zN+cMOR%{Ss>N3KF2ZMk6HQI{yqNOU+paXkg_vATjx0A;%)t0=hBbhGG;bZXtU-|dm zEop(9oct!8V7R0PpJiHfMaI=9X%ZKKL<*)ttaxPjQ5HXJ1o5)KT)QDie_5&oL2HfE zcJ1_MV^vB0aBqIq@ri@}rZ!&u?4XAl=cL9_P`ADWbPVBA%qf^APzGsGm&d5MjZUY@ zX1EsL)!D&nc(T>&Tck+M{=Syeid4Jlw`cJxG$2QmnT!!h52Mv8)WcdOW^B@8150}r z%6)i0m)C>n4n;%AyjiCj`lf%!$JL<~ruSEf}2q{)TvJDv4E8I!H5|tKJ8d zN;J!19IOdr1O^#R`6BCqyzAlhDiLB6PTOJHHQUOiq}(f>Y*t6ZxwzY}FjEt@M#WaE z#n~pj9y}fWH=Jy^_t6GOB~hp+lW*3(wsQXGJiPs}lW+Zr#Qk>TYie2|9F~W{ib_ZH zT1|J=LCuc52_76NZfTyvKXP3JoCe)jR@})ZWJsw34iSF<&Z|t`Q#Gpy$T`Qn)!d>^ z4=Kqiqg!)iu;|QqpuuMX(#RB@(l-hbnL(mj}F2LsgwwtRm$e z;>p;v3>W6B5e^6~`+PV6rhEexRyU)}uq-#Aj-Q-@FgU}0363wojO?NfvC8((hnsq< zx7;u`!puGdHiIQ+L;!#+bAd4m2AjcxGY0P9*ilZL_j{BI8~b2ky3mqzf1l`FC+$8u zLduO30@ck)Ij49|NI>Kd^Jg;OqTLmD)nOBao<2L1H@N}yH@yKu5k|sZ!nEI!JKY!0ajCD+xk}j#bA0onRWj}^<*xn%QMxQG_tvgu+zmapC zKg6h4eVcxj;O%PZNxjz8a+uVpYmTq7NX|(GICWQj-E|AtC(i2yS<|sk8>(yv2o(zU zj*pb5wEJ`jcKg)mHDHVeWeqqLw07+TJk1Ox)A!m*?d9g-@P^#;0PVdw7#QsW7iyy} zt3}0@Ej5xGSXJ#8?waSy(&*hQwxb8{WK0($)xL_g8qK6xsn^ainS4zuEmZbOdqw5h z^|PAVR3;AP;dc*=J6QUSvmK=m+~rYlRaJ4A^KxbtZT6K#lm?6qJ$xh)q!{NROG+pG z?$$=`v=#`^iTiaa?Zo-Fv&gR%I@4!oT{&~hFa=UFA6!fYYJ6g_`hSj(v*D4I6X@;A z)CjUxE?Xrk(^xGf_%1Fn2wlV)nh7@H&E}?C4>Bej2MtO5A-ioUoJ`P4BWCv@d$osVx0k5HbVIb`K9FSZDdmXbO+FU(VmfcVWw?4a^wERqZ z0%yOzT&+d;SdVZzwXMwf`aGc)US&7jxIATx3cGD4=>XEr+~F-M(abJK7bklpZV6oF(x}wL*Q}q_dWDYFXW0)b1?@Z43nRbxCV<&Fg$- z5FIy<)2tZE6Om?vBrl$HSa-Wp^G!321jwK`v-Mob-y^7Wr;;k>gIKXnsB#?`-M`3& z!I{g=T1}w#e~r`sVg)HGwt_g0;@8SXf;o$Ei&<;SI9p%!lFwWk5I~RBMY(V zJ^K}>W3fAQeiny1_x`~z`%$e0qm~Y}6`l;0l4#ux8|VY!oHZ;PsP*omSt;HqZRWlR zB6k-I@<;dK)sTdc2zSs=hM$?m-^~Es)sWOR?&~$VR7V^0=p1sJJ#O6gK+sk+xJO>X z*QYoH#I|RmwP$GM7fJ(8NmE`?TV7$-95N6Fg?(O=8YS1@`V~sA!1@*#00^CUOvMeB zseSBQWczm@0~;qT8Z4+l{ASD_tp%RZi>wTSCY*M*IB}=uewB=4DI^v-<=(w zlT8mztmRo1Du}aho(8}ElpxB677Mry!i(F7DdNaBM|`X!w%I$ri9Q}LyS~Ajp1tjo z5d@{<-SQ-GfkSFb8oAgf76~s7|Cxk{w{wQ4+$YcHvamH|Z2)@I6+u;P2Ot%wirk_6 z0BvLwDHTiI;>XCYOwl96=;V|UqLYe|Of!o32>N0{&3^)D!Zb*I$(R zfAZ_;-2Mqxr27X}-u@GdLvR0o!0XD>Q}R?(lByDtvJ;aNv}2Pq`$~^fGs^a~luC@u zs*H>c%&d*f%xdV2kOq9Uy`STz8JE7=t04 z|CF{%DAr@Y5X%>2lqK!%QIWi(XNl1l)$|!TXi7M zo){E*mvAjx*_@2YqN)4TM3_l9j?ANMA$G{LD--m-NEYvxLk$dEQixD|c;r$l0cO%; z9CuTj9JPCdIdx4+F9Nw98zH#$m$r`0Ns%XF@;3?>C;t|8{OdpXeC_{J7~xa!{iFK8 zzbXqDSzG)^ser$3j~#tT=KZ8?DSy(onEw0if`)%Z#EqPV?QCp5A%Zd%wkDs%OxI70 z{(ptVlT>s+nfYjZU~myM&7n3`+p|cA1RV%v+kV3dxNR2FF`mUe|3-M_WJvKfgba_MxO;Fc&AQY{-4lU+`y=o`gKO z@ICM$@I?XcL%(!1O+t_EO5nAC*YmZo@Kxguz<<)stuPilVX0HqWt;qoV0*>*TMdkDTiha*-sp3LP?b zAOR`-NZW9li*1_jgwtdTTE4~v%WB6Xc8duYAwVL63~#=^IW(YJa^8x5iH~+P>WPkN zC&0i;uXnO<8;S|7>m)G=yOJvSoa<*ZrG+u0o==^}kM?ek*}4(?ic{`vvXFr43w;ar z{BbB}Lh7ph+Hgy(b|INkII#sn*o+=mRl)}KUp7CMB>Q`90Fy2&Ng^=6B~v*i_6QKM z!#Prs0gIjFfJ-uw;E73*r686I2YI;+A%r}Xw*ziLVOOV>8UNRL!@fzzP94t17ms+N z1{Psaw?E`6)Obyc4_2D5G~d1poou5JOHbvoNp|39im|J;g8UYgLvu5ag3`yKX(S){ zq9Gc70hE?Vr!APSQq0c(Ev81=@d6hYgBhBQCPiu{7i9R6~sH#@ZA%TU6(SX zrr+}Kl&!y-BJ&TEnBvbSc=CDuEu{Nb%l)?|s9@mu37!8hUp6>W@UPMpq95i>T5zt1 z?V(n}GYV+nqJ3WnT}$aKKqY_K)ARa=pepOM+wK+8oTKrHPve9nb;I_HcJoOKKO`j2xWK&4P9U~HBfTN9ymDTn-VlD#rFs8tq*4-s z!7u&nc2A!UH1B`!cK`idWi6bXENso>?f+Vt3p$#89@ua;`BxGnNmqVBA8q7ghP}P& z+&Gu0n;A2)i^wR{-=92yfk}?FPd`8%sWOcXs63Cc&Cq!}jQdWcCy`Hj+mEyp!kk?~ z=Y%UgoJ@YnB|r0$wbJ+x5MFK&Iy%#V>Y!q10xQ{41vP4FvY9B=ln4{<5F6ysx(kA| z2-67T!)ii~{l?rSLP`gB;Ny2_pdL%x{t4oM&RTuNQ27*1vEC+A)Ly!3g@Ym$uF%sv zdGz;Ws_}4Q_$Q13p=QGGwh6@brmB=Vf)=ga>Kn_KCEgo_3A^=815>iLxJpQfq*ri( z^Y|XdoYBPP{CCZ|2<2KA*`ng|)MTprb}cUR)+>JEiuH#nZ|Dr^Iw}#k)v~q|ZFB&} zmI~$`QU>h!WOG4lm+#L0k1Ov%WXp68Sk!aO+e>n7Zb%C_L?&V62_5-DO=eCRiaKT> z1NYs4Envw3o!H4#WM>iOVxRZlNI;_zi-XivwN0x$0sSQ|yZsml1zA!d@)#x~fxjIj%rIH1V`Q_i0LLMg z-S_<{yoFY@Tnt{m?~2hge_G^|t}fsVFDgP7yoCutdwQ`3(*|- zIq~rQZ+gH#o4)d=J!Nb5*+1+JKAFw`Rk$TfW#$vvjP}R0-Ne8q@2)_C81Y=Jr*~mw+j+EYB}u`1(rqd(w0R#&WWp|B z$PHMNN(19wbh-BdOX1-@n7Ijh#3*mVD{#;wTkl(yI#!M9eD#)sWjy&fw@(x5ULssc z#6>Gu$jRrwUxwn_gEl`vumO)I11N&ZVfDWl%BQ}s9}$wZv-HMhp3E1>l$S+1 zt-a=Sm`z;W)Gg#SL65?K?3ue{;hpnGxL2HMawPU}KlSkI=)EM`3!0h-`M1VpTO1Un zt#8Fb@jR`<1Qd=HqdW9-6C@#C2Nq@cB-v4+J%uun){c2M_^%}I^o*-#FTYr9^h-43 zDdj?@;uAB}7}?kqcV+8&;}d=*vj8ETVTa4~qwkn_5pNq(;cN(uj9JhKg}xLV@DW8U z5&`wU$j81w{9gy|ubJ(H6yZ+%Q{g;6I!tRD@#FBvz86bS^rg|D%46+KxhDCYi-eQXPn}=G!bT&Gpjc0)|)ThluVM+ z=yU;^n+MsOzky%x{@lJo?!Zr>!mctKY={Cy1ADoS14{S;Ui19q3Cl1QQ9R#O98g?i z0N}yWT&CcvIdHBSL!`x!&S(}zM-%>H!sV@F$A-jNH$gjtDbx=_q9Z8x0ij+g%+Y07 zxTC?a4XI%dXI%P7R4Mt=JHxb+=H_KRI>?PF?!SxS$))(yUY6~day9cMe-)vF7j;jn z^j5dsZoE#cmVHT73^Ec5&b^OON4fBw>X{H3H)?Jbf%ABWGd=u1368Iu^~*VXp=04n zMo{nKJv^GMg5Bj1QSDb5Q^ovidJ!k3kuD2-1+y9O1lyyl<8t~Itu3dP57=mD0M$?r zF_|?mSr(39<*?wo!vAj$`Cnf}0Mq3Bn;HB zaz{Hv_w6xG&?E-~1cUrkD@l(vc0&3RG22L-UkLb)D-+qcZr~;Z$-%Obwg!GNB&B@` z)SG2j^Qwbh_xve^D%82CSDXK9IbZ(c(c_iZ=XE=$iqFi{wIKso8z%7kIO9I+db8W< z_w?1!N4DRW?>t*cbr5dVxn#rzUyV>@u!%JyCGYM$^sM#p^mK~lC9#l5cAf*HFtelqM%$T+vi?Dh0-czyF$9rpC*i}W(F9`IrQ>+&vj!$LyHN{Jw{M1AUTy zCadsJ>96^;%M~g=`PfJPR=7u@K?y-?DZzO*H5O;C@d^ z^UJ#7VOEwcv(#7LDOcwX@(jO_?`<`LJ7=F%0$vealnikU{acm62CT56Ne4Fd6#MX2 zpRbTu#Is79%e0>CE;`bM&&f$XAx#cdY=<~u%lrclr`ALMOoo=W~gYcNZIV{~UEg$aF0*BD6^F2>CeNnTX}J9!KzadQ4kmp+W!BaJXAWmzmGO z;VImJY7~a)7kRBrO~zWZ4t)B;Jh+9b;g(<_o7%1VX$i6#*{`V}eE?ij+b(}oiLiM`GF^xIaP zh$cxnT+WBNek$mL4O0u>nzmnw0Mw~{Trdr=(?)WAPVQp;_po}s5wN}^eJAS~Qmv3n zmSXJ%awpB*#xD%JPpE%#cVaFA1$Kp^uix(!ZEYwRjai(QJT!ww zGyG{hjDm>Z>s9HFcECK{>|}*xjy7b+ifoK~1-#|C8j+Wt@+YBh)}llrKbRjfnnhv6 zdDEHg)eKZ@uedah3aW?HM3l+fg4Mf*#WlWQNK8^6ip9gv!9b*nA&ND&G*YXpSogV5Yzx zd}qFZR%m{Y)<1VPi>4-00Yj5>`)y0)JSo0OZVd>!t1RCe5?&9l)aPwKC-6#KD(u)v^$P!LaC`wg9Zg-Sdx>5z~nU0o?HDF zb$7RZ`MtuBQ#SVyCR*tyU<6W%o3|*}{8=h{a+J!f)14|pAal2e%%;%YA5T&a!{lOA za?wQd#H*@3cSY^y4<7rg7RRp_Yr_0F7aYPz|CwO9LOWj*Zcugf=w4djSFa4yTNE{I z(cYy1(;BN++>8=Mr?Ypz7eh;i+`!y;r&Zn%ZmE%1i2>GpS{t0GIC4T$p@3q+PP#wc zE*LhNu*^rzB)-#wUJ*?K=ZX-nN#G( zvQxf+5P`?FGw~;aN69qAz+_A#zBR(0qCM4`cOA^xMcR${(JNv2d=W#Ey}|BOE43@^ zHN$tzHPiOg+2~j8`wpql8y(4dWc+Zaj`SI^8%3_8G=iBx)sxbQi`)B+rYEVff8zop z3WJNP$Kq^*mAq@i{LS&j2eQtX@C@DuePG@#BMJ=oQi-2hh+VqMHnq8e7kDjPbmGIN z1DM>ZGh0;~v&FNDK3YQzRBEOLQl+Jzp9N`@ugd9G@vP^SRj@56z--J`3KJY99JRKy zcq9~z5-q*qL%haz1QXrR4wK%Q>^1td^)jMd&jv8e>*7K_;gsT8P^4R0s_9mFMjI?e z{EQ+}Ze!oy>WkC656{B!h5h7=x|Gij(?P(fAU-?SY0{v1ERkP>8lP0-xJcip^A;q1 z;5VIO7r)lPnQNMxIMs3DcyIw^VOy0<#!L`|W zQ%2pQrrgDMIh+z=vK|7^T2$*b>i``QW;o|~jADj}&?0yE2HbU)Ic*d3?62EeUF&ik z;e{283NT{q;HY(Vp8|+jOW)hPwQ*Hkw&Ghh$@C4dY-8-wos0eH1p@^wW>oVp<`C2; z#iNFr=3tMjl@l0@es*NFs$(Q^@(ekjU)*qQBnf+im!rY8bc@lR;=N#9&%u~M6vtXLu@~Fw7~zShp5_G z{r{-wF4YO8&viT>-`F<;=I_wRx51&5W603Ec_g7EMMbJ;TEX@DE8mp&PmBTSGKoKK ze&|S`$53PX`hV;Uuk=UZacJAScuW;bUlFZ&9W;8e19j&sh)*|LUed_I|VT!LOhX3N<96LN9k=NMEKN%O^5{6`td^m+$qtxeOq z$`^t9t6rAz5@7Nd$IbWizO9F8(eEjlbcyz;soC2mCtE&xdX7<2k}Z5n99e6*wMNRH z`{8FBTk)}8%vlyK^5I5=^II0Vwi}U5di$h~<6HI4Ookj-y*Fn9thFAlTXyx0d{i=e zsZ<8V*kW2=7ABT6!?kCx)AHZTjJUq;MNxasQA~D*+kR7dASx3QObIuD7pu$NBgZIc z9b$Z%S?FV2LfZgYTp&ue5jTF_WycIRU^W5Hk=zGJ4}bQaV&GG>S5z`DPCEt=!Uj z#*(`$O2o?LO6V2vwl7at z@QRC!_!E(eb?t8&=QxNCW0SJDE^1Dw=y*q5K%%iKKe$%Y9*?T3b|%3<52b@!NOT&J z%ASlb0J6cQv;;*cpgdKkiawC^{TNFOEXzpZH+O{U@O5MmQx08(+}!|Lm=T7h#+%Xf z9;>QH7%!@!wW$MN<=fv@pd_ASTJfL$R~iDy-|I^J&GG){s`FodubQ^gf*SIlM68KA zQB?TBT>>J1qpzD7poxVF&@JC3{0k+8b4BY^#Z}^TG>_(gcfG@PK2#kRAvG%Z7fw3A z4hoySQoIVU`--a>uhmNzCxlIBFJ%Mm+m`@as5+nZSZ&)$&9$8*=1bxdA3e^ z;Z1`dirpv4?7{9~HV5f$-KB>&U^W5NMuKAe(bH#T0kN#aU8IHi?zF?XBlhBy+fjYU zeWCZKTwK!~xj%nl>I4-2v4$O+P;~v^>eG(D?pt9zy zRCBU=@K~i~#-dc{xoLO(_pDV34(N7s?WFn2D_SYeP3ZOdh_?JH40yT}j)%?CrpChb zU`0oWPW@S*$G)Ibi z0o-p_#Y^7jWw=dEjzjvU+Cp|SD$WJDFp$pkZdnZlr?oX~c`~TW76Y|c5OvKZP@DwX z@9OH%5)9Z{z2CaI4YUONO*vX_2B{W*luoTGv<_IM*BiJ0qz#Z4U-%eEkshR~Fg$L$ zZ_o9TA3ck`Dc>Qoo^Qn1&DYX1MuXs~lNQtb8Q2B;7%DDiP7QmtmmT>VmOx*o@Ava} zAvYs=WAD-(QtwH`Wu2IFlV+Z!{0-PggPs8So3a2fp;!2vh)c`|rXN;9+xmnIP1>;Y zSo*uiR&Mw%KMYm+)StEbI7nQ#BdAqFyd8I=lihTbCM)+`e@tp{dl9B(cX&qg!Tx|i zHEegYsGD`^LeeoEt4+?qx$_e0m?=eB&^-$&f(;8`M*0Je~WfkLFTSB_qLr#Un;^imfV0Hb73uErgp`POj|0alOCq z2;6?9j1Mr;FKD$Y=$1vE+J3sv$+SNN+ZwNSl7*#zb=CA8CPVdzy(6~t73U$*VKB)S z8s`<>*i>#55d3z}vdkygSRB_t6Dry2Xb*vpN??c^+&Xw47B>M`c#MUZSFvOcxp)j|3z&$SR; z+F4&$!&qzrgX|iVBh5d$!(2KP9!K_ZJwgl+<24>IL-rA_$2y>yBM=Nt%6)pSA>}N6 zdUDMtMXA)g7bGuQF0TDFt{hI0j&j{0cpgC#zhe+YGGG@wHfo-Vj(k^J2(_NmY|f4y z?+@bh4vx|`r!dCwZ{nqY%i!F7A4?nkS|~JayO4&{OZwY=*oOe3gkg=-M=RkJteO>H zx9zre%h8!))600?Dc=KK5{9C)wfW8x)zB1TgL1jLRIa)gm4Pr}sSZ?C>Sa}FYe*Z{ zEN|>}-#clZO}+gO!+*NHnbtZpC7*6@@qbU={%utM*FNU|!%|FA()}xW%h#aU;3_NI zn7-#0NhL;Qi}vFiiTQW50N6O*XLd=z<*2EeDFxX_K~JH4F#j{yYeBdh`xg{A3s-{a ztd8UC2|l+!Z}0E$JIFu0jcZQ_hKfVtLu>#SWh(QTOvdG2HjphSPvFAcR7tJa4?IHK z_i`d>L#CUDiWycG*ZYN5-D5!pyN_d|8bF6EXdv_EY|Unqk`M<;_O}4aktvN3!BP(f zR6&mT&mw(KZD(uz1?}TJaohvmm6VG|V(?RKhW z>)r?39>@;pkaPt_u;Zn z=`T`(jm${Y`Pw0ZjG0Uy{rX-ce+I548vA_wL_#|j1Al&oZf#_zEo=>yr=mCD8p@x- zq;)c(^%Xja99ruciXiQm;EhtNOHQsTc|)*78aFwyHkkeuM?s71ODWI!%= z2v|m57c?QM(^v2Q8GhBo&XLYV7X#h6)j`eqjB(6R+=6x^k3=wcr|#4-kj+M?7<+U5 zw8e7p7VZ2Iy^ntDt7_g!F6YY@R8m~sXJ{j!(IBsTbj3DT;DqZUEjEOP}W!cw(XdQd{t4{@N0BwKhO zeeYB zVc&2TNFZWt5nZ~pRv(mNw3&)Drj=d8&|xNdkWhjw46#p5 z&?EOXo>8;KZHAKTvolyyERY%)Iq)!jvF1)L!DGm9k^}-I_dXjpje2|}0(^63ov+oY zR&?O}?)PwY71kIDZek>DCOW*=tV#3yX#GP0HBnl1VR<;JzpxB0KQMvNnOW^N)yRsP+0ZKbhI5@cghs85i$Ah~><{GmaoK>F$l<7@@m zkNf-6)!~Os~H2L#;zXe3dEjx@Z#c8XS=1y?F zKFIG3e)}7mPCFz@&LA+z7;#~M`-;CYqK`|S+3bCN262^o!+br+PIQlx3pFEMSs6pr*6=;25LB?-~(_9{L z;s!oQ1Z|C!UI^bwd9sS>Oi4MZvcJ0TAxFFGp2w(1t!OVzh;*ZFN#Q3V9*cpG1QVze zd_!ElcJk+yXeETb@~Vg$vS*N~^w-${i}`B$ibQI6wnDm7F*P?T=998nMq{|rK@F@Zm<3U5fGY`% zXmfVDmWWt{&b<}QH4l+yWm!L#gP*m-_Gr7(NsD9Js2@Y;?lTHE2c|9DFQu#eg|WON zj*MHb48iyGp_&zy*mN5nEq*XsWa2q5ty7=Pi>+&i5e5{Dhl+k;c<4(c-C&PEu#CAu zc8YVr>+DM_C**$?v4OEB7Ktd_2{{P0dNP_TyCE)-isKd|;O3*`C*#>fd_`_I>Teq+ z+2)^CZHq`qhRZ8W97J|DcipI)7)TM`>y52gDKDQecIrjAPxt~ zo^U*Bf?+AH-dGojd#b%dDvFGaVKNKZOEeI}O7KYekg5q097f_!`HbPoT$L!y-GNCd zfuOyJ|V<~p1&NNY+KF+1* zZOG=s*BI+0srNv0PV`44+OjL4SK=?Xw-2P-K%cvVEXvOkF4w{tXAD#_;kASq>DdDs zp{v*fic>86eSyX6%0QB%yzR-Vdk6%P zX#Go#)u;|e$@|xuz^JSIpu&Cp^gzpk%q<`%7Hj$JArr@J{h-k@-wqs#|!ZC8>KY#S1c$RQFW1-Cu({B=)HVxRsi2fV}0A7ruZiglW8%MvYmV={vSa>gxq*v zb!8uQfM6lpZxYLeQD>82Tnlo=Gnfa$JcoRgP$qlv<=F$pCQ1>*oX{rC$$l!w>V-qT zT$qeZBlGYE0z=h;?o3 zrBp6&42|3-X9WWM!c9sqJ4A-BRQKj_ONI85_C_Q3NN1&PmPq4}XTTzm&LaFHaHs;` z1i#;I<-ME<;-nx7eCfU5r{gIx9exFgj$2kb7h?C>;82T7^15Lf7izUOA67+i~zUjk) zP@wYF$hNr9`Dg{tazc^aAcq(`4G8rwb1S@0kE6CkazSzQ1)O zFT8x>g2ZU1TqglAUV;EjFe1OV=}%4geW5O>ZL1H^Bh$CAHMTQ$(Eqb9Ql9)@4zWyb zG;2E1bvLR#A@Ow0d3QPl;SxFmBqjor*U!LG4d%@q5&-(0o@+e`$v1D^u0%0UX|ScB z!H@+LU3W(tcSpG$uXf8VSD!I|dinghETh;ysW*3P9IS#}gGr{vTA{alfSx1=6}wK* zJ8E*6vpTLg7;Me$e#c4iH!gkImhvR4_TZg7i0Kpe6d3S4R2l31>Ni!JHxp-ynWOr2 zpW>J-nq!&PgF7w(k%>3O%FUry6XHHK9lGe69tCI7mU@@cbjtWKO)2t1d`!?XhSiV# zfZ@m0)T`C#N;T@Q4{c~R5yF-UhtiJA6ME+y;1sz|2ooqNRqEszXX}hL97RBNn@f*{|d*bZD zi={%gD9boJ3+=+CHW|j~4=l*wMv3eolu6AJ`Z~z!VCf7kUsf63=wz^USJV~}2P|Kj zFqnx%?#vyB;m*c3@pN5zAJ7tv zIPu7!u_;{rbp-Oyt3fwJ0s`s<#OWgY7rphnu}~G-NnyHHi~5{BHugD5G?4F0BKQH_ z7$5%0fA0pGBMr*Qi(}Ga__UJs4nG-v){Ta7nUjsiwDV-l%DFC7rQU> zn4KP9uBb1%TDmT}n5yr$UnM0COTm#{ZEhZMyOy`kEF7Ml);g|yxoJceVh)wvnSi_V zy!|4~gFmoaj`fu`;Xwxfa4Som^Z4yVVX*2ZPMV#uCMV|6%zT$t(hT#JacW8*=kC5j zM}W-jOM%U3PSmsaFGqKMUcT63+G0}MBuaz(gn=J9ZTvEFa;|)m1n+c{Y5N-FRthCV zoKv$a)?I^!*l@rwBuwh^jM->l(%r4Dm&p!_K6DEyT++Ts=gK;%X8SW_e+bmA0+cV+ zI+r|8wUBJBg#%tjm+h8(=9xwsnr&_Gxt-eJIg3`Nb-2usQpRCEb=N+GkDN3T2cbHtjVCS}!+3ye@#T-t26W&Ci0RsX6Cdu--aVtL)mO z)qg_eOlg_!8_9sF-&4mShPd60FPI zJ~~2%$)uN9F1(&Wx{OJ8Cd6tOs?X9pV3dXlJ9yfi$+d## zhb7OWZCPh1hg+BiM)E7M2Jm`Lb1h|PWM?goiy0<1ZZf8# zCa&0MK(xoe+?Y634zmSqXWP$wV8Gr;(I~~R@LQWTG5levz*@>-N`$TIf!M<`W=jUl zP>xN4N*L1owyb7uHg}|%q^LB&SiUOVjN_%_A-W$pl88eC0^hh4ydBMBsD_ofC~(cM zt42n&FhoUK4bmgH*b}Si2_cK^$3v|JvMe1$9f zu{x7OR(ixG`Pj-h>MH#XR0e9rey4he+PVT7*4cZ1&+q@c&(W~TB*&_8A zeqBU^!PCXx<8O($cPt=a8D=M(BG&~O5sBHI{Tc(q4t?2tjK66zlWxo$Y?wrQAk&Q{JeJP7`w$7e8W&?R|_(}%PXF1AOvt$rz}j3OFQwmJarzxTrTbVm@#oP}AEc=bMYx%IEnO>%?rc1D`G zb+45})SH3B4YK;;ZgZ1!fPhTAU`izo8fX|ELSyz` z%y1SDxxIF8BGOWk=L>a7gec9Lxa=kJ{_G}nu7^EL`F#c`;JQ5q5D;S%noB-J1ZK4g zA!u~LN$tj;>PfIo4u-ARk?2^})k27kO{Gg<$wiaRlU0_&dP5ySH;;Rms0x*oYgOwb+g}-6DftAw}7|73aWwqB*#0Fk%#g=akp-mZ*fc1z)Y>^KLBh`Q##f>rQ z-}MC*tYTl5?6lfgzD@HszA9)Jg#{0hJr`kcbh6^y8_;REP5o;10p*4{A#Z)neJ4ls zc7GrDHQm>i{fM5@2!43TE9(}k%#x3s?-f;fUB+lVeVcX+v(N^)%Q2CUVxWvR*P1Hq ztde+%o;P*yp?+CoF3Y{J%gcFW_AlOJp1JLfOgiqO@C#^@fOAJr&&x%Hn*qL5ptsfs zuQ4#AJEnTW?u62?WYLRNvTS{s>Dx4ptHdjk5XXtSdW&mtt<=~mx;e0@Cl@TJ+RVQ~ z?qHXcrGmykp-G^^&~NhCBF&sSK61RVw4^dSqe7G&Dxt(4zd=m0H(6KlK^yvU_;~Rw z%|K5e5ks|gb{MDEmT#sy5DlhYrFmPkBb>Gr0l(a8CAo}1f|Poak$l!oZQePUiQ1uZ zDY-Sj=>k|2$2lWkE!Kw@Pkeb<5=Rk#-k?YB66SsRBC32p67zXLiIsYbravW26gniE zP^UQf4)x#`Yka6j8EfJ2s6z;ML5Iw9XvK*}t90VTh3x3E(M$el^+Y(>&s&7nY`S~H zvO-2^RU{uJSa$s@7GCWkuYvDp>k1YI`uc?7)Z@PuF(Aq`A3HBmv1LwlJ3fpf54(k9 z#ms-#vRG=NpC0`@_A+0kkN6p6`^}VTNcI{37tZ_ep3pK}o-68s4rqQC2$*Mw`*f7Z zsf?}!b1zG?$}noMj`gH*a=XHoyYD-EWb;f7UU6j;Ym^lqFd76Zshwq(OcL)-*D<*r>u&zKlR5PU!Ub$Q6^?!y|+2b^6VOSt-_^ z%Zj-Kwug+V*7zm|^-FH%If>ATTAX%Y2v4`;K3YdBfAuY*jdSIZdth&*-na%thggU> zP55NW&^X>@q{{1@91&BWP^0ykyA)$7v^*l-h%!9acAw`0CMETx06Yk#7#z8THCA+7 zhUPF&qhd0}h4K`maf~H-aJiLv1LF*6Q$UPNE#MTmqBsZAE**)!*B}OgptX6AFlbH` zelmf<&@?UQz0J^Ih~f)wfk>SPh`Xxe^0mjV3yem;!b5_K zkI%6kdAHdv<@x33tG5nv1oE{wa}q>mujS?BRlQt|r39Vv!+WOtjvcSZ+4BY6Ub}eY zTaMje$@;HO3L4^Vkbg<B<2*zN2goBm-=O4XuI)X% zz8YgjIC}QMPWaXS^%mVpR&{YJt3D!y0YvG}?3bJEHi1&w582Qa?-gh{CC8h%AzxQq zy0%a@4Tu&V(W81d;YXNj=U5SLFRQZy zcfd)~HK@`fUIVR$Ge@wFD|9>2YRaIGqp3+MM+JK>8dKZLGigfG+99ioRVoRoVslF# zUm$_*H`j!FfE8U+2;sj5Ps^r{%!G){lSvojYDmo1kg!e{)m#$eawb0BFrOMpvm-st zE4~3bUKcf{$4dbq;}I=4i_+P_;=@A72OQtmpG1$@Z+u^ck449?ZOtgqVY1@ zZ{+Z~!Beiu8ARl`GonjbyIZ{;AYB-|Ic*t;Fw5UH66Tu$L71&IVN2jhJbyt8ssWy+ zx&@ttD$isCH5DnDR49BffwHnzO;I)ANC) zqJa+%=sRO~U-7z6>44p9f(o-b!H}`kqdQ`HeCWOL)NHn# z3#r4>m3ZUNbbZ8LV;grw{=x!j{nk}jl*AJdC!ymr(jA)7k^G;sgLduwG1(3$&BUS6@z zUh0GLzCvxTO~N_kT6+R&_HD=U$IC-^yI{#ZLn4B$OrtpNPzNnYu)JlGebSoAke5EP z(|yL~wczW7k}q&ua+zxN(p0h{XNtEaZj!t^hnDDG$;Sd4O*Msc*C1l6A&8wABG$!s-l)&{$j{CzLL{$%t%8a?!@hpW!{iWjf>Yoo7&hK0?1+v^3&y z&upm#Spa!u@s;{3_SKFk@3T90D$j8HT$j_XI$-pnJ>Cvt@Fo9`Y5SSwd!D{C0eA2~ zRigX#kWuD=`g*hEgNM(_;~R>Wg-?Rv$IJMlT^+(j35&_)LT~O1YYQuAqk+Xx4 z`4!k>wiaW~7pr$8UyIR9jtj1LK_-i_j(D&E-S>K^Es^9I(%H{|quk_fUgw4=P&L2P zI^jclwgL@I zdvSq#qc{xFX@(SE7zCq_{GR1L4(La2c|HzoaDIqXWy|ca1$miYg`gH>Nix5p-6-1- zk*@|y-JSw;V*CLbw`dN$>57KR1!tJ&%&@jw(lkFDBB^A3w<1jD8|{#Q!?3 z%>XaRcyw7XRr+3S1RH@dXwNIbnm{#eR2H&ej`zEwwdyEV}2i}E` z*{yiz!bZG-S70@4O}2YL3m<(S$ZFVpEpW#!a4k=GpPX)f1J5&&12C*o0ye^#{)MTE zgx>%VPv9>%2;0BxR;BO$&u6;tu^#(y4-A_k=p(cbA9P$+b`XP{8^nMRvR!ZsgQF?# zbQz1I@EP%qrW;|fM0PNK2fY5v`r@3bXdeb?myaCRORF5aE4GUn?QLIyUiF56p-y5| zCGL}pD>D=mhC9QOp((^E(lBlvcvKH?7jHPRb~*K+!&VbEY%drr+Ygg#)R>vtuNwLj z+76wiuCaD)*;U<3y(4TrPzRwC>$-EOHV7?f*@@9_*qCip-|mcd(USsKmkA~G+|_>@ z+Gh#ecb(g`<6Ng=?_8`OYl0Vs6N*VjNVaiEd8iZHUOtcg44r?mpPo_Exo6d8a$Bow z3BqraMah5_^R))Eo{eTK%=0#M!S@ZF^i%PRa>k6ASgfv5uH6zZvO{UFS0g`vyj^KJ z{aQ$NtqkVqIvtNghbP{n2u5FmyPg<3uw8)~mj-%E#UzEJ59wRCZW-G2wIjNeVPTtz zE_9eUu*FStC}J&xdLh$f+&i`TF5xk_NRNS8tw;@|`chYF(@0;&-=5lb`oDBMKv8nZk_Bn;-R z_kk)ffhEmn;VKZG<=I7$_-~yzU}T+&u$ab}xCx7_7MR!sK7M4L{Za ziY3XMotWpD>CIu({=}D4bll)52GHkI0hvWyX=|=123Z2G~+6Oe6;8X%oW2>KhkL(BxYwr)y4F zz3F-$z5Umd9m@;Fqw`gITq}^c}ShpKft<&t#Fi5X{#66orY0f}mq9sVL zH*2O`a$4`;_ZWZ5F5vL_U}=7%jdqhF3BvK%i+}YMESElo+jdiDImb%~kYhE|^wpYV z9!vJlBCa~cb2Zu%R=rTRC3wF#?BV3klJX(m%<(U-XUsZ>-i4t_e)Y>2DBm=7>IVv# zMW1ly$tX$|KAQAlRy0P#ghKzo0CVP|3BsS%RKxd4?JVZt9!lEM<=#WHrDl7q&y{Le zGAKeDgVP2hdM7%921ZA#(8vj(3`GrtyquSDx+o)f!?p&}&WFmd8jT$T;x z0ZcEz>y^tj8;@}~m6yq7NSMPSCk1yOPT(Z)0~gnlKE|PKW8U?}pmQ_r64>~$V>$IXD3UmIY)&R|H#^@?lB$Ry3=4u+4VVCNa7WV4s5o?}>7y9N1iI6^pNX6i!4 zXI^voflM;=zo!^_oBH_{4hFdaj6$|fdoVU!XKT`2$eiarh6+PFakM0!_8N4)hrl9_ zh(v&IoM8YSxMWCy4`S1Yso$-X~g7AWAwNqd|hG5-WL{GUJcQm=1cq9A{$Lf#)gT~ z#S;v}RO;QiO)(hDC)^ssSZv1r(Ra|l?m#$^Z7942h>BuC0|9aUKCJ&8E9T#9f&u~q zI$|lJJix(7F(&Q!WU-Kyio>7+!&9&^sgB7QC(xj!p)f3($Joh2ahs8(8BOYx zBFZVJg|@m=8I@TmAZet2pK@x6WM{*>>9n7BZ6xRl?$h&B62@ zAckY(`YMX?u|O&r*<8jtvAk;Cfjw{Nyay{zjNU?Cqg-c)n_YyXV>FUb-#&y zK3}ldPx+zj3buc~F?v-Q+JR^TO>XcY!Pz#CE9ZE7!&9?UOPS8O$O`AGT4aRgy(3F{ zr;#VRyZ2%YK-&gGM0Vlb*^7Mr;kRntx|pYeh|vjhd~&@sZ{#Yev%8hAgp3%k&V+4M0v^eO$__iD zj{53M-z;|ZJTMnlj1_Mv$ZrrLoRk1zj%+AfG^lsdXVw-`ylX9k#hqqZi+?>p`Y6Tg<9Ydgr!N1wjyeIZzZj%xfsGG%lhUg7GP(PJ=HbS5Z$_mP|f zjKg_m5N1o<7Or8!>b4L}gUbg(kK zlLv;*vYe;dW%@M|3t9(sBJS-UsyEXtJ5rVr-y>JS-puI0-puMSqhe#sJwC8CW7Y9zxoj)blmO&LRZU-w})h;h5yZSZ%D#DWIVP{N~Zg# z=#_?B9}Y9y_~Lx#AP|wEyE_BB1w%d^BUFj{g^E@P1)(A2S%!`ITcIWxy?6_AO#zya zc4KpVV{>77{ygv!N3~hvOw)ANTM|v&Cao7(++vM5ustP*^7Fe)#ND^=Xlzm@+?cPB zHeo?BE{DxyRSS<*1**1HJ81=$_xmP4Uoh}k-%b6ba`f$#QfyiaY71a)CIHOMG`|mA zzd2?8eA*&hUj6?1CwG`x14fr-G(;|98 zeI#qU$qbf=5^@J@>3=+Wk%uDgmXyYEpLXiD%E8qB==S*REh06g-m6z~QiMJN@OShX z+1mjjDdIG_QC{i2v@~Sa>K>=>8>ri_x2keC+CspgkX(n&td;rmtA?%;S3dg{D*GMM zQtuT)b?ImgtwR|!c_jE$56}pfyF^rkZ8PSPNOU4;sq!2tujc-ge2U+~_SGYRS`w)Dhz*RzvdialDZ+5wRt(0}qn2 zHi3;aB><1wVEp=)HvtpRfDCf&cFD$@E>oXkXuo|IhE2jpxvd&DiCVLZB(&t>I z2Gc0APSg4QuLer3n>+nUzY@Ifcfe$f)Vhm5G;7%*dPRM|RM66P%$`42)3}@Drw(__ zxR??AVA?dWswDl{&of9HBZ=zxOu6N)ZGjxceWwjpabp3D+zYI#^>mW(ZhHrf-5>(z zlKK0ud!1Z7EBQ(e>e&Vss-K-0x%X5HGl~6cBC1u!7=oBMEp!!nvLi@oidDudLs$a* zUu}mQwo%s6tlw@cv4}CjTtiFNa=|c>Z@zqqkCnJ`ECIJr+ao_3MfgZ(Sh#`r9D}S& znTu;xYq?y9?bKdy3unJFiVQHS+U=)CB$8k?mpb*u zJfbEN@xULK<)?ig|Ct6pe1xFKfI*-VX8V1>k#Oc$5*DIvXULpq=TNsus7(3oe79rk zq5Nfvm7(M_>%r@cWv|lLsd|CaxnXMLgg2S8g;@CF-35QuoU2b;wRd)}53xJAM{(_NQ;||h zB=7)5}m37tuE{8(oj2!aw#7Zh`^kwqF7SBo?U?E?c zhJ=?;(W_A)!T__zak@fEch%1Kr(;gZU6Osh-_F3j8!N|}!oUKVx6oL9h?~pWR+iQq zh$6hGjH(m-+GwxCmHYzCy4~buN!shUZO(OB#@ah{(#CNYNR8Dp6~Ce5(Ufw(6Hn;Q z5r++5wA(Q1>Uo6}KBKqx$+QB&9w;=j@Tt9>V zTEBwhXgdc0k4QJb7s0;@V<(_*U}>W-Vr*k;CvUIwz5f6D`t4CNmq%6xoRY7yvaU7~ zgMC*wC+5qi1;Jm;hX9Qjg%oTa$2wOptui^SH#=`u^bl0ng%Tr4_pj_)Wy{f}$*#=r77`8Z=m`G^)G;3-= zk`1G0!HG1sB@lD4n2bssGhh{?*7ChzJntBSq$5(p5bD@JmOztt;HBkT!7MoNOk$~4!>lz} z8xvtfy`RCruS!rkSIcni@3=A&C)XGmU}m=-=|({tbWzDC2jSqHbVxxrqNa8Q`DnKc zSqBn26Jhr3G(**$f%YXph0JLOIf=ht!)wz?ybiOQbuvnf41Y1;bn>1Q6rG+-#eE2Y zm$Rcv(RhlvOUwQBOmfD9z@&a|650UOI+4YwFj?;*@+8a$-!H=nct-jun_Qq&5=1&l z>qWcKtdZ_O+Y~4l9E^{0rfr8 z!Z@;uO7|8#c$kxZSO3ao!PKri8SIUr0BY*%>iig*b4{leF0DePS~$mf>W#1GVES{L zvuj`BZ`!-1Q@g2&E;6Aexxzqwvs)(n;WOS}U0l0F8n79k6lewac>2?!$sT=pWEydI z%2=4x3D*?FR~PWo>;u=s&S&Y=jdSb5l&dAh?hC^e@A2?H z#k@oQ_`&_=`E%%rpbPSevfC+HfUwhxUSq5vL@np0$PYSuH5Xi?C|?IUnLw`TFKqC$ zvge|4qO}NDofooQ@ly8;f)8NBsuaU2SxDwM8O?lGLOB8-^b=G<+X5h^kjxp9v!mgk z9T5b8;JU|ciR)m!Mj%mba&CB8DmG;+O6!oR)Na*4Y!Em3$EuBX0ppW!SLyIp}tB3Lc5y#8vg&`qc7j%Pg1N~)&IFFn3 zSGJfh_`i-Ju|Ql&-#n|o0LEyJ-^XZqXIndc^M7MgNQ)Vg=;A{O_&8T=URyU~GA+Es zB7iK^?T;RXhW?uF)xJkE-efchGTEfSiiENcG=4`Q61g!#A%C}OD%1JL$C1>=7SEQp zXC2SX5(wbKiOf*4RQ*PP%}_Ii2|Nd1l6{2KTeyqjs~hSQ%Um$TTaj8u3~}YOiFb#}Vb@Tvt`+q2fwGX=^3*mQDXf1&E{)4eX7Aiqk-L z$Ypz+fe@%dCXg_2u4pDs_p3f-6z|Pv66R$_9#y5i_{<#q$0kmtwc{1ArIWT@Mu4z0 zhEqw|76|NL`dA7VH8Wp`c%w|kwA)sIb6l>;4FLy_W^YtsB~c;2v%RO|1ME0JN>J_S zR>J9{Qrr3tQZuwcO@o|}Smn1})OfMBXC=|u(SnZ9WOEf70iG|i)u4)aOpnwaL4Ivg zT2vz+a6of51B^wCzc=Ym)9!c2>fe@^@8nl4CtjgE$WWp{+jcA|Fe9_!(6b)6F=0rP zBqv6hLmI%lHuH5g#i`pa(%$jjZiJHY+<@NzzPQZi^?X5$C(`k+Q%~J?Qx{h~JsyCq zfciwR7FikRMzc*eF&${8Xqh3Bl+!P=XZ;jftp(`0K8%r;IB@UdX@%XF-BH}}xJoR) zCHR7z_0n86)xd7Y-*2h%RaUV}bkJPVBSBs*z4Van!)G)%LdDCjM1g7W^hwAqgnwoqFN{ahS1VOpL#z5IdLpx4sY^qT^T8S4q}i zcEch!1ldo-p-?1KI_Wnvs$Ctf-3%S8n>pGa-0tBB0)!Dqf|w_eP{)0O#H#q|0<0uE zD!djon5YCg61}*9dxf2>W&MKgf$<>3=%-RFrvwNF$I>RkHAoEmi=9bhMv9|z+bRi7 zizyZ5(e!dMF|4cblv$=*`sk+*%^u4ANwsJzLjf_Tonr2aI>$Oe&(*Q1L(UYm24cH2 zCaP^b#90;E=%BclGz03oP30NL6m#Ah)G38T!AykZQ;IOsp+iBbhO^&cu)_szTo}O9 zMv6;2lfXzf#WU!4Nm(Wrl|hOz)-1HRqf$zDy3D7j#jXxUx0GxXVNSlP)o9U}*gbN_ zWW8OB566+!z{GRsSgs;3kPwhW*Pm`{HAhDO6!i?|(D3tmT34uQ&$m{r^J(fd17VBmlO53H<*I809%Yxf}ul$Pr-T0}%fw z>^)$3_+X4=ji5Q#d^XuyB+uBNNTWA~pEw%78 z@58WKBHu!2-vSJJzvdkeAZq%Dyet1D%>l4=7#JJc1L9``V#)tG?|Lr7t1*Bo;Rd`* z^nYg@@T~E^L--@~)Akets709lw~XgG(>EyrG7bc&oo_?N-&c+I0_q>pr7R8qYb}i0 z9EP9*98D|$W&U<9>hG(@+Z><)@`qaZMfUE`#b;lsTgC>wVn={cfZ%UHz_Z4?7m(jS zU;<7B+G(4a{TXe!Ln^o%P?_%lmHBHs;RE``AJ7CWE$zPPZdgfc8(RR3u0PZ^o^}DT znR=2*K>s2J6!n{C!rxbo_X~jN-yfjAcL8B1eO>$igin8p>W7tETm?WC0H9L+4GDPG zc#8`D5%sT^;yd=YO#iteo@(y?4PE2SFY`y-@74O>hM%Vzhd=NL0R#FUO8-mK|2M_M zr?v4^Kko+%welZX{&~cCDx32I&iBoKX3y^f@E>Q;pY!)^ck8L@%@07-xBp!O=PAm! zRNr37Z`U{7n7^)X^BAV~FQxnz!{%w?rz$dkC$I4q`#tgBegZ$O*PmElpTa*?2KfO$ zsry^reuDk}b;?Z^FOFcP5z1MzXYCt3jZ`_`VV+PvwwpB-V*;5LH#M!)8MN=sPygr1=U}b_P?s@ zY5d9`B!Q0qg5;m0Sw1b%({O)3$a-Ap#72PxsJ&ATyQ!hWvYH`V0EcJL*ph@pSL< z2NhY>KT-XUx%BCl-4ED+>VJa$K4ARA2Hw*GJT>h9U>dCdjp^z4!%ubhKMM5J*!+Vg zt?@USpJ2Zi==jD1h7jz91(n*Rm \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/gradlew.bat b/spring-cloud-function-samples/function-sample-functional-aws-routing/gradlew.bat new file mode 100644 index 000000000..f6d5974e7 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml b/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml new file mode 100644 index 000000000..be0aab22c --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + io.spring.sample + function-sample-functional-aws-routing + 2.0.0.RELEASE + jar + + function-sample-functional-aws-routing + Spring Cloud Function Sample for AWS Lambda + + + org.springframework.boot + spring-boot-starter-parent + 2.6.0-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + 1.0.27.RELEASE + 2.0.2 + 3.2.0-SNAPSHOT + + + + + org.springframework.cloud + spring-cloud-function-adapter-aws + + + org.springframework.cloud + spring-cloud-function-web + + + org.springframework.boot + spring-boot-starter-web + + + + com.amazonaws + aws-lambda-java-events + ${aws-lambda-events.version} + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.springframework.cloud + spring-cloud-function-dependencies + ${spring-cloud-function.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.springframework.boot.experimental + spring-boot-thin-layout + ${wrapper.version} + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + true + aws + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/java/example/FunctionConfiguration.java b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/java/example/FunctionConfiguration.java new file mode 100644 index 000000000..9aa1eea0c --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/java/example/FunctionConfiguration.java @@ -0,0 +1,59 @@ +package example; + +import java.util.function.Function; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.function.context.FunctionRegistration; +import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.MessageRoutingCallback; +import org.springframework.cloud.function.context.MessageRoutingCallback.FunctionRoutingResult; +import org.springframework.cloud.function.json.JsonMapper; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.messaging.Message; + +@SpringBootApplication +public class FunctionConfiguration implements ApplicationContextInitializer { + + /* + * You need this main method or explicit example.FunctionConfiguration + * in the POM to ensure boot plug-in makes the correct entry + */ + public static void main(String[] args) { + SpringApplication.run(FunctionConfiguration.class, args); + } + + public Function uppercase() { + return value -> value.toUpperCase(); + } + + public Function reverse() { + return value -> new StringBuilder(value).reverse().toString(); + } + + public static class RoutingCallback implements MessageRoutingCallback { + @Override + public FunctionRoutingResult routingResult(Message message) { + String payload = new String((byte[]) message.getPayload()); + System.out.println("==> Will be routing based on payload: " + payload); + return payload.contains("uppercase") + ? new FunctionRoutingResult("uppercase") + : new FunctionRoutingResult("reverse"); + } + } + + @Override + public void initialize(GenericApplicationContext applicationContext) { + System.out.println("==> Initializing"); + applicationContext.registerBean(MessageRoutingCallback.class, + () -> new RoutingCallback()); + applicationContext.registerBean("uppercase", FunctionRegistration.class, + () -> new FunctionRegistration<>(uppercase()).type( + FunctionType.from(String.class).to(String.class))); + applicationContext.registerBean("reverse", FunctionRegistration.class, + () -> new FunctionRegistration<>(reverse()).type( + FunctionType.from(String.class).to(String.class))); + } +} diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/application.properties new file mode 100644 index 000000000..eed9c5e95 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.org.springframework.cloud=DEBUG +spring.functional.enabled = false diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/log4j.properties b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/log4j.properties new file mode 100644 index 000000000..f644d953c --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/main/resources/log4j.properties @@ -0,0 +1,20 @@ +log4j.rootCategory=DEBUG, LAMBDA +PID=???? +LOG_LEVEL_PATTERN=%5p +LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] boot%X{context} - ${PID} ${LOG_LEVEL_PATTERN} [%t] --- %c{1}: %m%n +# CONSOLE is set to be a ConsoleAppender using a PatternLayout. +log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender +log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout +log4j.appender.LAMBDA.layout.conversionPattern=${LOG_PATTERN} +log4j.category.org.apache.catalina.startup.DigesterFactory=ERROR +log4j.category.org.apache.catalina.util.LifecycleBase=ERROR +log4j.category.org.apache.coyote.http11.Http11NioProtocol=WARN +log4j.category.org.apache.sshd.common.util.SecurityUtils +log4j.category.org.apache.tomcat.util.net.NioSelectorPool=WARN +log4j.category.org.crsh.plugin=WARN +log4j.category.org.crsh.ssh=WARN +log4j.category.org.eclipse.jetty.util.component.AbstractLifeCycle=ERROR +log4j.category.org.hibernate.validator.internal.util.Version=WARN +log4j.category.org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration=WARN +log4j.category.org.springframework.boot.actuate.endpoint.jmx=WARN +log4j.category.org.thymeleaf=WARN diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/src/test/java/example/MapTests.java b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/test/java/example/MapTests.java new file mode 100644 index 000000000..289a8c9ff --- /dev/null +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/src/test/java/example/MapTests.java @@ -0,0 +1,31 @@ +/* + * 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 example; + +import org.junit.jupiter.api.Test; + +/** + * @author Dave Syer + * + */ +public class MapTests { + + @Test + public void test() { + + } +} From 24f4699b50f048a75763b67555490d8fd827cc5b Mon Sep 17 00:00:00 2001 From: Mark Sailes Date: Thu, 11 Nov 2021 19:31:35 +0000 Subject: [PATCH 13/41] Send a user-agent header with requests to the Lambda Runtime API. Adding the author tag. --- .../adapter/aws/CustomRuntimeEventLoop.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java index c8ee8590b..9cc703f4f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java @@ -45,11 +45,14 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; +import static org.apache.http.HttpHeaders.USER_AGENT; + /** * Event loop and necessary configurations to support AWS Lambda * Custom Runtime - https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html. * * @author Oleg Zhurakousky + * @author Mark Sailes * @since 3.1.1 * */ @@ -60,6 +63,10 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { static final String LAMBDA_VERSION_DATE = "2018-06-01"; private static final String LAMBDA_RUNTIME_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/next"; private static final String LAMBDA_INVOCATION_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/response"; + private static final String USER_AGENT_VALUE = String.format( + "spring-cloud-function/%s-%s", + System.getProperty("java.vendor.version"), + CustomRuntimeEventLoop.class.getPackage().getImplementationVersion()); private final ConfigurableApplicationContext applicationContext; @@ -92,7 +99,7 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { logger.debug("Event URI: " + eventUri); } - RequestEntity requestEntity = RequestEntity.get(URI.create(eventUri)).build(); + RequestEntity requestEntity = RequestEntity.get(URI.create(eventUri)).header(USER_AGENT, USER_AGENT_VALUE).build(); FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); RestTemplate rest = new RestTemplate(); JsonMapper mapper = context.getBean(JsonMapper.class); @@ -125,8 +132,9 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { } byte[] outputBody = AWSLambdaUtils.generateOutput(eventMessage, responseMessage, mapper, function.getOutputType()); - ResponseEntity result = rest - .exchange(RequestEntity.post(URI.create(invocationUrl)).body(outputBody), Object.class); + ResponseEntity result = rest.exchange(RequestEntity.post(URI.create(invocationUrl)) + .header(USER_AGENT, USER_AGENT_VALUE) + .body(outputBody), Object.class); if (logger.isInfoEnabled()) { logger.info("Result POST status: " + result.getStatusCode()); From 703f82a132243a4e48df129e4d4b8126eb220e83 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 12 Nov 2021 12:58:06 +0100 Subject: [PATCH 14/41] Modified version determination for user-agent, added test --- .../adapter/aws/CustomRuntimeEventLoop.java | 44 ++++++++++++------- .../aws/CustomRuntimeEventLoopTest.java | 4 ++ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java index 9cc703f4f..3fbececdc 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java @@ -64,9 +64,9 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { private static final String LAMBDA_RUNTIME_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/next"; private static final String LAMBDA_INVOCATION_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/response"; private static final String USER_AGENT_VALUE = String.format( - "spring-cloud-function/%s-%s", - System.getProperty("java.vendor.version"), - CustomRuntimeEventLoop.class.getPackage().getImplementationVersion()); + "spring-cloud-function:%s/JAVA-%s", + extractVersion(), + System.getProperty("java.vm.name")); private final ConfigurableApplicationContext applicationContext; @@ -85,6 +85,22 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { }); } + @Override + public void start() { + this.run(); + } + + @Override + public void stop() { + this.executor.shutdownNow(); + this.running = false; + } + + @Override + public boolean isRunning() { + return this.running; + } + @SuppressWarnings("unchecked") private void eventLoop(ConfigurableApplicationContext context) { Environment environment = context.getEnvironment(); @@ -205,19 +221,13 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { return value instanceof Collection ? (Collection) value : Arrays.asList(value); } - @Override - public void start() { - this.run(); - } - - @Override - public void stop() { - this.executor.shutdownNow(); - this.running = false; - } - - @Override - public boolean isRunning() { - return this.running; + private static String extractVersion() { + String path = CustomRuntimeEventLoop.class.getProtectionDomain().getCodeSource().getLocation().toString(); + int endIndex = path.lastIndexOf('.'); + if (endIndex < 0) { + return "UNKNOWN-VERSION"; + } + int startIndex = path.lastIndexOf("/") + 1; + return path.substring(startIndex, endIndex).replace("spring-cloud-function-adapter-aws-", ""); } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java index 32d64bd28..a57748063 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java @@ -27,6 +27,7 @@ import org.springframework.cloud.function.adapter.test.aws.AWSCustomRuntime; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; @@ -46,6 +47,9 @@ public class CustomRuntimeEventLoopTest { .run()) { AWSCustomRuntime aws = userContext.getBean(AWSCustomRuntime.class); + Message replyMessage = aws.exchange("\"ricky\""); + assertThat(replyMessage.getHeaders()).containsKey("user-agent"); + assertThat(((String) replyMessage.getHeaders().get("user-agent"))).startsWith("spring-cloud-function:"); assertThat(aws.exchange("\"ricky\"").getPayload()).isEqualTo("\"RICKY\""); assertThat(aws.exchange("\"julien\"").getPayload()).isEqualTo("\"JULIEN\""); assertThat(aws.exchange("\"bubbles\"").getPayload()).isEqualTo("\"BUBBLES\""); From 3aa37ba19738004984082fcae8acea807e8aac74 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 12 Nov 2021 15:23:25 +0100 Subject: [PATCH 15/41] Change format of user agent to be aligned with the original PR from @msailes --- .../cloud/function/adapter/aws/CustomRuntimeEventLoop.java | 6 +++--- .../function/adapter/aws/CustomRuntimeEventLoopTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java index 3fbececdc..499297c64 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java @@ -64,9 +64,9 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { private static final String LAMBDA_RUNTIME_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/next"; private static final String LAMBDA_INVOCATION_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/response"; private static final String USER_AGENT_VALUE = String.format( - "spring-cloud-function:%s/JAVA-%s", - extractVersion(), - System.getProperty("java.vm.name")); + "spring-cloud-function/%s-%s", + System.getProperty("java.runtime.version"), + extractVersion()); private final ConfigurableApplicationContext applicationContext; diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java index a57748063..f2ba21d9f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoopTest.java @@ -49,7 +49,7 @@ public class CustomRuntimeEventLoopTest { AWSCustomRuntime aws = userContext.getBean(AWSCustomRuntime.class); Message replyMessage = aws.exchange("\"ricky\""); assertThat(replyMessage.getHeaders()).containsKey("user-agent"); - assertThat(((String) replyMessage.getHeaders().get("user-agent"))).startsWith("spring-cloud-function:"); + assertThat(((String) replyMessage.getHeaders().get("user-agent"))).startsWith("spring-cloud-function"); assertThat(aws.exchange("\"ricky\"").getPayload()).isEqualTo("\"RICKY\""); assertThat(aws.exchange("\"julien\"").getPayload()).isEqualTo("\"JULIEN\""); assertThat(aws.exchange("\"bubbles\"").getPayload()).isEqualTo("\"BUBBLES\""); From 6def6291f2ce7a6c0fffb2a4d4a03f9548486d7a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 16 Nov 2021 13:55:14 +0100 Subject: [PATCH 16/41] Fix type discovery --- .../cloud/function/context/config/FunctionContextUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java index af3313f93..c06ac28ab 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionContextUtils.java @@ -55,7 +55,11 @@ public abstract class FunctionContextUtils { .getBeanDefinition(name); actualName = name; } + else if (registry.containsBean(name)) { + return FunctionTypeUtils.discoverFunctionTypeFromClass(registry.getBean(name).getClass()); + } } + if (definition == null) { return null; } From ec731664aa30ab6e857b33d5b30daef322a0e4f7 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 17 Nov 2021 15:43:46 +0100 Subject: [PATCH 17/41] GH-769 Fix regression with FunctionArroundWrapper Resolves #769 --- .../cloud/function/context/catalog/SimpleFunctionRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index 20bb6d23f..a255f88b9 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -1128,7 +1128,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry { output = enhancer.apply(output); } - if (functionAroundWrapper == null && ObjectUtils.isEmpty(contentType) && !(output instanceof Publisher)) { + if (ObjectUtils.isEmpty(contentType) && !(output instanceof Publisher)) { return output; } From 2efb79d4707f5aab614c91ad9f046c22fe8b8e3b Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 17 Nov 2021 15:58:56 +0100 Subject: [PATCH 18/41] GH-727 Fix NPE in Azure FunctionInvoker output conversion Resolves #727 --- .../cloud/function/adapter/azure/FunctionInvoker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java index e8b2eb1bb..01c9f5911 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java @@ -186,7 +186,7 @@ public class FunctionInvoker { @SuppressWarnings("unchecked") private Object convertOutputIfNecessary(Object input, Object output) { - if (input instanceof HttpRequestMessage) { + if (input != null && input instanceof HttpRequestMessage) { HttpRequestMessage requestMessage = (HttpRequestMessage) input; Map headers = null; if (output instanceof Message) { From 71f3af825d75d6a288a520bb48078dece78bf99b Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 18 Nov 2021 13:29:07 +0100 Subject: [PATCH 19/41] GH-727 Fix Azure's Supplier logic to avoid NPE Resolves #727 --- .../adapter/azure/FunctionInvoker.java | 22 ++++++++++------ .../adapter/azure/FunctionInvokerTests.java | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java index 01c9f5911..ee1f1dd07 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java @@ -110,7 +110,11 @@ public class FunctionInvoker { public O handleRequest(I input, ExecutionContext executionContext) { String functionDefinition = executionContext.getFunctionName(); FunctionInvocationWrapper function = FUNCTION_CATALOG.lookup(functionDefinition); - if (function == null && StringUtils.hasText(functionDefinition) && APPLICATION_CONTEXT.containsBean(functionDefinition)) { + if (function != null && StringUtils.hasText(functionDefinition) && !function.getFunctionDefinition().equals(functionDefinition)) { + this.registerFunction(functionDefinition); + function = FUNCTION_CATALOG.lookup(functionDefinition); + } + else if (function == null && StringUtils.hasText(functionDefinition) && APPLICATION_CONTEXT.containsBean(functionDefinition)) { this.registerFunction(functionDefinition); function = FUNCTION_CATALOG.lookup(functionDefinition); } @@ -128,7 +132,7 @@ public class FunctionInvoker { resultList.addAll((Collection) resultItem); } else { - if (Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType())) + if (!function.isSupplier() && Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType())) && !Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getOutputType()))) { return (O) this.convertOutputIfNecessary(input, resultItem); } @@ -145,15 +149,17 @@ public class FunctionInvoker { @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerFunction(String functionDefinition) { - FunctionRegistration functionRegistration = - new FunctionRegistration(APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition); + if (APPLICATION_CONTEXT.containsBean(functionDefinition)) { + FunctionRegistration functionRegistration = + new FunctionRegistration(APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition); - Type type = FunctionContextUtils. - findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory()); + Type type = FunctionContextUtils. + findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory()); functionRegistration = functionRegistration.type(type); - ((FunctionRegistry) FUNCTION_CATALOG).register(functionRegistration); + ((FunctionRegistry) FUNCTION_CATALOG).register(functionRegistration); + } } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -186,7 +192,7 @@ public class FunctionInvoker { @SuppressWarnings("unchecked") private Object convertOutputIfNecessary(Object input, Object output) { - if (input != null && input instanceof HttpRequestMessage) { + if (input instanceof HttpRequestMessage) { HttpRequestMessage requestMessage = (HttpRequestMessage) input; Map headers = null; if (output instanceof Message) { diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/test/java/org/springframework/cloud/function/adapter/azure/FunctionInvokerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/test/java/org/springframework/cloud/function/adapter/azure/FunctionInvokerTests.java index 1f31d49ce..cc024d7ea 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/test/java/org/springframework/cloud/function/adapter/azure/FunctionInvokerTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/test/java/org/springframework/cloud/function/adapter/azure/FunctionInvokerTests.java @@ -134,6 +134,16 @@ public class FunctionInvokerTests { assertThat(result.toString()).isEqualTo("[foo1, foo2]"); } + @Test + public void supplierPublisherBean() { + FunctionInvoker handler = handler(ReactiveSupplierConfig.class); + Foo resultSingle = (Foo) handler.handleRequest(new TestExecutionContext("suppliermono")); + assertThat(resultSingle.getValue()).isEqualTo("hello"); + + List resultList = (List) handler.handleRequest(new TestExecutionContext("supplierflux")); + assertThat(resultList.size()).isEqualTo(2); + } + private static String consumerResult; @Test @@ -209,6 +219,22 @@ public class FunctionInvokerTests { } + @Configuration +// @EnableAutoConfiguration + protected static class ReactiveSupplierConfig { + + @Bean + public Supplier> suppliermono() { + return () -> Mono.just(new Foo("hello")); + } + + @Bean + public Supplier> supplierflux() { + return () -> Flux.just(new Foo("hello"), new Foo("bye")); + } + + } + @Configuration protected static class BareConfig { From cd967c793a8a28b8754cd6fa91f14a2574c9e6c5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 18 Nov 2021 14:59:40 +0100 Subject: [PATCH 20/41] Cleanup of Azure sample --- .../adapter/azure/FunctionInvoker.java | 11 +++-- .../function-sample-azure/README.adoc | 47 ++++++++++--------- .../src/main/java/example/Config.java | 4 +- .../src/main/java/example/EchoHandler.java | 8 +++- .../main/java/example/UppercaseHandler.java | 10 ++-- 5 files changed, 48 insertions(+), 32 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java index ee1f1dd07..787f4a6dd 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java @@ -106,9 +106,7 @@ public class FunctionInvoker { binding.setValue(result); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - public O handleRequest(I input, ExecutionContext executionContext) { - String functionDefinition = executionContext.getFunctionName(); + private FunctionInvocationWrapper discoverFunction(String functionDefinition) { FunctionInvocationWrapper function = FUNCTION_CATALOG.lookup(functionDefinition); if (function != null && StringUtils.hasText(functionDefinition) && !function.getFunctionDefinition().equals(functionDefinition)) { this.registerFunction(functionDefinition); @@ -118,6 +116,13 @@ public class FunctionInvoker { this.registerFunction(functionDefinition); function = FUNCTION_CATALOG.lookup(functionDefinition); } + return function; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public O handleRequest(I input, ExecutionContext executionContext) { + String functionDefinition = executionContext.getFunctionName(); + FunctionInvocationWrapper function = this.discoverFunction(functionDefinition); Object enhancedInput = enhanceInputIfNecessary(input, executionContext); Object output = function.apply(enhancedInput); diff --git a/spring-cloud-function-samples/function-sample-azure/README.adoc b/spring-cloud-function-samples/function-sample-azure/README.adoc index 66cc7dbad..024fbd244 100644 --- a/spring-cloud-function-samples/function-sample-azure/README.adoc +++ b/spring-cloud-function-samples/function-sample-azure/README.adoc @@ -2,32 +2,19 @@ You can run this Azure function locally, similar to other Spring Cloud Function this time by using the Azure Maven plugin, as the Microsoft Azure functions execution context must be available. ---- -# Build and package +# Build and run package $ mvn clean package - -# Previously, for other examples -$ mvn spring-boot:run - -# For Azure Functions -$ mvn clean package azure-functions:run - -or - $ mvn azure-functions:run ---- -The `uppercase` function takes `Function uppercase()` and its expected input is JSON map, therefore we need to -provide the appropriate content-type (in this case `application/json`). The function iterates then over each element -and returns its `uppercase` mapped value. +The `uppercase` function is of the following signature `Function, String> uppercase()`. Its expected input is JSON, +therefore we need t0 provide the appropriate content-type (in this case `application/json`). -Test the function using cURL or HTTPie and notice that the URL is formed by concatenating `/api/` +Test the function using _curl_ and notice that the URL is formed by concatenating `/api/` ---- # testing with cURL $ curl -H "Content-Type: application/json" localhost:7071/api/uppercase -d '{"greeting": "hello", "name": "your name"}' -# testing with HTTPie -$ http POST localhost:7071/api/uppercase greeting=hello name='your name' - # result { "greeting": "HELLO", @@ -35,7 +22,27 @@ $ http POST localhost:7071/api/uppercase greeting=hello name='your name' } ---- -The same is for `echo` function, however it will take any input since all it does is just echos it back. +The HTTP headers of the incoming request will be copied into input Message's MessageHeaders, so they become accessible if need to. +It is done in implementation of `UppercaseHandler` which extends `FunctionInvoker`. + +NOTE: Implementation of FunctionInvoker, should contain the least amount of code. Everything should be delegated to the base FunctionInvoker. +These implementations of FunctionInvoker are really a type-safe way to define and configure function to be recognized as Azure Function. +Look at it as _configuration with the callback_ (e.g., `this.handleRequest(..)`). +---- +@FunctionName("uppercase") +public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, + HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + ExecutionContext context) { + Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); + return handleRequest(message, context); +} +---- + + +The `echo` function does the same as the `uppercase` less the actual uppercasing. However, the important difference to notice is that function itself +takes primitive `String` as its input while the actual handler passes instance of `Message` the same way as with `uppercase`. The framework recognizes that +you only care about the payload and extracts it from the message before calling the function. + There is also a reactive version of 'uppercase' - `uppercaseReactive` which will produce the same result, but demonstrates and validates the ability to use reactive functions with Azure. @@ -73,10 +80,6 @@ curl https:///api/uppercase -d '{"greeting": "h # testing with cURL $ curl -H "Content-Type: application/json" https://function-sample-azure.azurewebsites.net/api/uppercase -d '{"greeting": "hello", "name": "your name"}' - -# testing with HTTPie -$ http POST https://function-sample-azure.azurewebsites.net/api/uppercase greeting=hello name='your name' - # result { "greeting": "HELLO", diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java index beafa72af..8bfd1c78c 100644 --- a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java @@ -37,8 +37,8 @@ public class Config { } @Bean - public Function, String> echo() { - return message -> message.getPayload(); + public Function echo() { + return payload -> payload; } @Bean diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java index 44c8e3336..784f0e86c 100644 --- a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java @@ -26,17 +26,21 @@ import com.microsoft.azure.functions.annotation.HttpTrigger; import java.util.Optional; import org.springframework.cloud.function.adapter.azure.FunctionInvoker; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; /** * @author Soby Chacko + * @author Oleg Zhurakousky */ -public class EchoHandler extends FunctionInvoker { +public class EchoHandler extends FunctionInvoker, String> { @FunctionName("echo") public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { - return handleRequest(request.getBody().get(), context); + Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); + return handleRequest(message, context); } } diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java index cc7f6d1b4..983c4b350 100644 --- a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2018-2021 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. @@ -26,17 +26,21 @@ import com.microsoft.azure.functions.annotation.HttpTrigger; import java.util.Optional; import org.springframework.cloud.function.adapter.azure.FunctionInvoker; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; /** * @author Soby Chacko + * @author Oleg Zhurakousky */ -public class UppercaseHandler extends FunctionInvoker { +public class UppercaseHandler extends FunctionInvoker, String> { @FunctionName("uppercase") public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { - return handleRequest(request.getBody().get(), context); + Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); + return handleRequest(message, context); } } From 090b8762916f921987d83197b78efb9cf1ed23cf Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 18 Nov 2021 16:17:15 +0100 Subject: [PATCH 21/41] Fix documentation around ExecutionContext for Azure More cleanup in Azure samples Resolves #759 --- .../main/asciidoc/adapters/azure-intro.adoc | 84 ++++++++++--------- .../function-sample-azure/README.adoc | 11 +-- .../main/java/example/UppercaseHandler.java | 1 - 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/docs/src/main/asciidoc/adapters/azure-intro.adoc b/docs/src/main/asciidoc/adapters/azure-intro.adoc index 4d56cfa82..4a94264d8 100644 --- a/docs/src/main/asciidoc/adapters/azure-intro.adoc +++ b/docs/src/main/asciidoc/adapters/azure-intro.adoc @@ -3,65 +3,71 @@ === Microsoft Azure The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure -framework into the user functions, using Spring Boot configuration where necessary. Azure Functions has quite a unique, but -invasive programming model, involving annotations in user code that are specific to the platform. The easiest way to use it with -Spring Cloud is to extend a base class and write a method in it with the `@FunctionName` annotation which delegates to a base class method. +framework into the user functions, using Spring Boot configuration where necessary. Azure Functions has quite a unique and +invasive programming model, involving annotations in user code that are specific to the Azure platform. +However, it is important to understand that because of the style of integration provided by Spring Cloud Function, specifically `org.springframework.cloud.function.adapter.azure.FunctionInvoker`, this annotation-based programming model is simply a type-safe way to configure +your simple java function (function that has no awareness of Azure) to be recognized as Azure function. +All you need to do is create a handler that extends `FunctionInvoker`, define and configure your function handler method and +make a callback to `handleRequest(..)` method. This handler method provides input and output types as annotated method parameters +(enabling Azure to inspect the class and create JSON bindings). -This project provides an adapter layer for a Spring Cloud Function application onto Azure. -You can write an app with a single `@Bean` of type `Function` and it will be deployable in Azure if you get the JAR file laid out right. - -There is an `org.springframework.cloud.function.adapter.azure.FunctionInvoker` which you must extend, and provide the -input and output types as annotated -method parameters (enabling Azure to inspect the class and create JSON bindings). The base class has two useful -methods (`handleRequest` and `handleOutput`) to which you can delegate the actual function call, so mostly the function will only ever have one line. - -Example: - ```java -public class FooHandler extends FunctionInvoker { +public class UppercaseHandler extends FunctionInvoker, String> { + @FunctionName("uppercase") - public Bar execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, - HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, + HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { - return handleRequest(request.getBody().get(), context); + Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); + return handleRequest(message, context); } } ``` -This Azure handler will delegate to a `Function` bean (or a `Function,Publisher>`). Some Azure -triggers (e.g. `@CosmosDBTrigger`) result in a input type of `List` and in that case you can bind to `List` in the Azure handler, -or `String` (the raw JSON). The `List` input delegates to a `Function` with input type `Map`, or `Publisher` or `List` of -the same type. The output of the `Function` can be a `List` (one-for-one) or a single value (aggregation), and the output binding in the -Azure declaration should match. +Note that aside form providing configuration via Azure annotation we create an instance of `Message` inside the body of this handler method and make a callback to `handleRequest(..)` method returning its result. -If your app has more than one `@Bean` of type `Function` etc. then you can choose the one to use by configuring -`function.name`. Or if you make the `@FunctionName` in the Azure handler method match the function name it should work that -way (also for function apps with multiple functions). The functions are extracted from the Spring Cloud `FunctionCatalog` so the default -function names are the same as the bean names. +The actual user function you're delagating to looks like this + +```java +@Bean +public Function uppercase() { + return payload -> payload.toUpperCase(); +} + +OR + +@Bean +public Function, String> uppercase() { + return message -> message.getPayload().toUpperCase(); +} + +``` + +Note that when creating a Message you can copy HTTP headers effectively making them available to you if necessary. + +The `org.springframework.cloud.function.adapter.azure.FunctionInvoker` class has two useful +methods (`handleRequest` and `handleOutput`) to which you can delegate the actual function call, so mostly the function will only ever have one line. + +The function name (definition) will be retrieved from Azure's `ExecutionContext.getFunctionName()` method, effectively supporting multiple function in the application context. ==== Accessing Azure ExecutionContext -Some time there is a need to access the target execution context provided by Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`. +Some time there is a need to access the target execution context provided by the Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`. For example one of such needs is logging, so it can appear in the Azure console. -For that purpose we propagate `ExecutionContext` as Message header under `executionContext` name, so all you need is access it -is have your function accept a Message and access this header. +For that purpose the FunctionInvoker will add an instance of the `ExecutionContext` as a Message header so you can retrieve it via `executionContext` key. -Spring Cloud Function will register `ExecutionContext` as bean in the Application context, so it could be injected into your function. -For example -```java +``` @Bean -public Function, Bar> uppercase() { +public Function, String> uppercase(JsonMapper mapper) { return message -> { - ExecutionContext targetContext = message.getHeaders().get("executionContext"); - targetContext.getLogger().info("Invoking 'uppercase' on " + foo.getValue()); - return new Bar(message.getPayload().getValue().toUpperCase()); - }; + String value = message.getPayload(); + ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext"); + . . . + } } ``` -With Message you will also have access to additional Azure meta information as Message headers that come as part of your request. - ==== Notes on JAR Layout diff --git a/spring-cloud-function-samples/function-sample-azure/README.adoc b/spring-cloud-function-samples/function-sample-azure/README.adoc index 024fbd244..f20f7d7e3 100644 --- a/spring-cloud-function-samples/function-sample-azure/README.adoc +++ b/spring-cloud-function-samples/function-sample-azure/README.adoc @@ -25,9 +25,11 @@ $ curl -H "Content-Type: application/json" localhost:7071/api/uppercase -d '{"gr The HTTP headers of the incoming request will be copied into input Message's MessageHeaders, so they become accessible if need to. It is done in implementation of `UppercaseHandler` which extends `FunctionInvoker`. -NOTE: Implementation of FunctionInvoker, should contain the least amount of code. Everything should be delegated to the base FunctionInvoker. -These implementations of FunctionInvoker are really a type-safe way to define and configure function to be recognized as Azure Function. -Look at it as _configuration with the callback_ (e.g., `this.handleRequest(..)`). +NOTE: Implementation of `FunctionInvoker` (your handler), should contain the least amount of code. It is really a type-safe way to define +and configure function to be recognized as Azure Function. +Everything else should be delegated to the base `FunctionInvoker` via `handleRequest(..)` callback which will invoke your function, taking care of +necessary type conversion, transformation etc. + ---- @FunctionName("uppercase") public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, @@ -40,8 +42,7 @@ public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, The `echo` function does the same as the `uppercase` less the actual uppercasing. However, the important difference to notice is that function itself -takes primitive `String` as its input while the actual handler passes instance of `Message` the same way as with `uppercase`. The framework recognizes that -you only care about the payload and extracts it from the message before calling the function. +takes primitive `String` as its input (i.e., `public Function echo()`) while the actual handler passes instance of `Message` the same way as with `uppercase`. The framework recognizes that you only care about the payload and extracts it from the `Message` before calling the function. There is also a reactive version of 'uppercase' - `uppercaseReactive` which will produce the same result, but diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java index 983c4b350..c95233f30 100644 --- a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/UppercaseHandler.java @@ -42,5 +42,4 @@ public class UppercaseHandler extends FunctionInvoker, String> { Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); return handleRequest(message, context); } - } From 97fc63d1023cff5b9d126f7c071eb780512fbb7c Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 19 Nov 2021 16:04:10 +0100 Subject: [PATCH 22/41] GH-771 Remove ApplicationJsonMessageMarshallingConverter from the list of available converters Resolves #771 --- .../config/ContextFunctionCatalogAutoConfiguration.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java index 35149a36b..4a6d944eb 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java @@ -109,7 +109,9 @@ public class ContextFunctionCatalogAutoConfiguration { if (!CollectionUtils.isEmpty(messageConverters)) { for (MessageConverter mc : messageConverters) { if (mc instanceof CompositeMessageConverter) { - mcList.addAll(((CompositeMessageConverter) mc).getConverters()); + List conv = ((CompositeMessageConverter) mc).getConverters().stream() + .filter(c -> !c.getClass().getSimpleName().equals("ApplicationJsonMessageMarshallingConverter")).collect(Collectors.toList()); + mcList.addAll(conv); } else { mcList.add(mc); From a60ed7c0cc77c096fdf80708a346310bd070c213 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 25 Nov 2021 09:14:54 -0500 Subject: [PATCH 23/41] Re-enable ApplicationJsonMessageMarshallingConverter --- .../context/config/ContextFunctionCatalogAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java index 4a6d944eb..4d3a7eddc 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java @@ -110,7 +110,7 @@ public class ContextFunctionCatalogAutoConfiguration { for (MessageConverter mc : messageConverters) { if (mc instanceof CompositeMessageConverter) { List conv = ((CompositeMessageConverter) mc).getConverters().stream() - .filter(c -> !c.getClass().getSimpleName().equals("ApplicationJsonMessageMarshallingConverter")).collect(Collectors.toList()); + .collect(Collectors.toList()); mcList.addAll(conv); } else { From e8f7ae5387c320c0198cda3370cf474b0164859a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 29 Nov 2021 17:10:49 +0100 Subject: [PATCH 24/41] GH-773 Fix wild card type matching in SmartCompositeMessageConverter Resolves #773 --- .../config/SmartCompositeMessageConverter.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/SmartCompositeMessageConverter.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/SmartCompositeMessageConverter.java index b243049d6..fedbe5e98 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/SmartCompositeMessageConverter.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/SmartCompositeMessageConverter.java @@ -74,18 +74,21 @@ public class SmartCompositeMessageConverter extends CompositeMessageConverter { if (headers.get(MessageHeaders.CONTENT_TYPE) == null) { return null; } + Object value = headers.get(MessageHeaders.CONTENT_TYPE).toString(); String[] contentTypes = StringUtils.delimitedListToStringArray((String) value, ","); for (String contentType : contentTypes) { if (!MimeType.valueOf(contentType).isConcrete()) { List supportedMimeTypes = ((AbstractMessageConverter) converter).getSupportedMimeTypes(); for (MimeType supportedMimeType : supportedMimeTypes) { - MessageHeaderAccessor h = new MessageHeaderAccessor(); - h.copyHeaders(headers); - h.setHeader(MessageHeaders.CONTENT_TYPE, supportedMimeType); - Message result = converter.toMessage(payload, h.getMessageHeaders()); - if (result != null) { - return result; + if (supportedMimeType.isCompatibleWith(MimeType.valueOf(contentType))) { + MessageHeaderAccessor h = new MessageHeaderAccessor(); + h.copyHeaders(headers); + h.setHeader(MessageHeaders.CONTENT_TYPE, supportedMimeType); + Message result = converter.toMessage(payload, h.getMessageHeaders()); + if (result != null) { + return result; + } } } } From 4f2c24bcf96debac0d548b9056077ebf9e64513d Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 15:26:23 +0100 Subject: [PATCH 25/41] GH-768 Add tests to validate proper map values conversion Resolves #768 --- ...BeanFactoryAwareFunctionRegistryTests.java | 24 ++++++++++++++ .../catalog/SimpleFunctionRegistryTests.java | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+) 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 d750df4b4..71f343d44 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 @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -644,6 +645,16 @@ public class BeanFactoryAwareFunctionRegistryTests { assertThat(resultList).isEmpty(); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testGH_768() throws Exception { + FunctionCatalog catalog = this.configureCatalog(SCF_GH_768ConfigurationAsFunction.class); + Function function = catalog.lookup("echo"); + + String result = (String) function.apply("{\"ricky\":{\"name\":\"ricky\"}}"); + assertThat(result).isEqualTo("{ricky=Person: ricky/0}"); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void testArrayPayloadOnFluxFunction() throws Exception { @@ -1121,6 +1132,19 @@ public class BeanFactoryAwareFunctionRegistryTests { } } + @EnableAutoConfiguration + public static class SCF_GH_768ConfigurationAsFunction { + @Bean + public Function, String> echoToString() { + return persons -> { + for (Entry entry : persons.entrySet()) { + assertThat(entry.getValue().getName()).isNotEmpty(); // would fail if value would not be converted to Person + } + return persons.toString(); + }; + } + } + public static class Person { private String name; private int id; diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java index 517b08dfa..3c2f04e49 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java @@ -16,9 +16,12 @@ package org.springframework.cloud.function.context.catalog; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; @@ -119,6 +122,29 @@ public class SimpleFunctionRegistryTests { assertThat(instanceA).isNotSameAs(instanceb).isNotSameAs(instanceC); } + @Test + public void testSCF768() { + ResolvableType map = ResolvableType.forClassWithGenerics(Map.class, String.class, Person.class); + Type functionType = ResolvableType.forClassWithGenerics(Function.class, map, ResolvableType.forClass(String.class)).getType(); + + Function, String> function = persons -> { + for (Entry entry : persons.entrySet()) { + assertThat(entry.getValue().getName()).isNotEmpty(); // would fail if value would not be converted to Person + } + return persons.toString(); + }; + + FunctionRegistration, String>> registration = new FunctionRegistration<>( + function, "echo").type(FunctionType.of(functionType)); + SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, + new JacksonMapper(new ObjectMapper())); + catalog.register(registration); + + FunctionInvocationWrapper lookedUpFunction = catalog.lookup("echo"); + String result = (String) lookedUpFunction.apply("{\"ricky\":{\"name\":\"ricky\"}}"); + assertThat(result).isEqualTo("{ricky=ricky}"); + } + @Test public void testSCF640() { Echo function = new Echo(); @@ -578,6 +604,11 @@ public class SimpleFunctionRegistryTests { public void setName(String name) { this.name = name; } + + @Override + public String toString() { + return this.name; + } } private static class Words implements Supplier { From 11a870d6a60ee183beac670796c860c32a05d72e Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 18:53:58 +0100 Subject: [PATCH 26/41] GH-766 Initial support to expose FunctionCatalog as actuator endpoint --- spring-cloud-function-context/pom.xml | 25 ++++--- .../catalog/SimpleFunctionRegistry.java | 2 +- .../FunctionsEndpointAutoConfiguration.java | 46 ++++++++++++ .../function/endpoint/FunctionsEndpoint.java | 72 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../function-sample/pom.xml | 4 ++ .../java/com/example/SampleApplication.java | 8 ++- 7 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java create mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java diff --git a/spring-cloud-function-context/pom.xml b/spring-cloud-function-context/pom.xml index fb23fec49..bbffa76d4 100644 --- a/spring-cloud-function-context/pom.xml +++ b/spring-cloud-function-context/pom.xml @@ -43,9 +43,9 @@ true - com.fasterxml.jackson.module - jackson-module-kotlin - true + com.fasterxml.jackson.module + jackson-module-kotlin + true org.springframework.boot @@ -69,7 +69,7 @@ com.vaadin.external.google - android-json + android-json @@ -83,7 +83,7 @@ com.fasterxml.jackson.core jackson-databind - + org.jetbrains.kotlin @@ -109,10 +109,17 @@ true - io.cloudevents - cloudevents-spring - 2.2.0 - true + io.cloudevents + cloudevents-spring + 2.2.0 + true + + + + + org.springframework.boot + spring-boot-starter-actuator + true diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index a255f88b9..d9cb73ddd 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -433,7 +433,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry { public boolean isPrototype() { - return this.isPrototype(); + return !this.isSingleton; } public void setSkipInputConversion(boolean skipInputConversion) { diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java new file mode 100644 index 000000000..912f5f2b9 --- /dev/null +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2021 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.context.config; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.function.context.FunctionCatalog; +import org.springframework.cloud.function.endpoint.FunctionsEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Oleg Zhurakousky + * @since 3.2 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(name = { + "org.springframework.boot.actuate.endpoint.annotation.Endpoint" }) +@ConditionalOnBean(FunctionCatalog.class) +@AutoConfigureAfter(EndpointAutoConfiguration.class) +public class FunctionsEndpointAutoConfiguration { + + @Bean + @ConditionalOnAvailableEndpoint + public FunctionsEndpoint bindingsEndpoint(FunctionCatalog functionCatalog) { + return new FunctionsEndpoint(functionCatalog); + } + +} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java new file mode 100644 index 000000000..c27820e6e --- /dev/null +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2021 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.endpoint; + +import java.util.LinkedHashMap; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.cloud.function.context.FunctionCatalog; +import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; +import org.springframework.cloud.function.json.JsonMapper; + +/** + * + * Actuator endpoint to access {@link FunctionCatalog} + * + * @author Oleg Zhurakousky + * @since 3.2 + */ +@Endpoint(id = "functions") +public class FunctionsEndpoint { + + private final FunctionCatalog functionCatalog; + + public FunctionsEndpoint(FunctionCatalog functionCatalog) { + this.functionCatalog = functionCatalog; + } + + @ReadOperation + public Map> listAll() { + Map> allFunctions = new TreeMap<>(); + Set names = functionCatalog.getNames(null); + for (String name : names) { + FunctionInvocationWrapper function = functionCatalog.lookup(name); + Map functionMap = new LinkedHashMap<>(); + if (function.isFunction()) { + functionMap.put("type", "FUNCTION"); + functionMap.put("input-type", function.getInputType().toString()); + functionMap.put("output-type", function.getOutputType().toString()); + } + else if (function.isConsumer()) { + functionMap.put("type", "CONSUMER"); + functionMap.put("input-type", function.getInputType().toString()); + } + else { + functionMap.put("type", "SUPPLIER"); + functionMap.put("output-type", function.getOutputType().toString()); + } + allFunctions.put(name, functionMap); + } + return allFunctions; + } + +} diff --git a/spring-cloud-function-context/src/main/resources/META-INF/spring.factories b/spring-cloud-function-context/src/main/resources/META-INF/spring.factories index 845272816..ca1caf4a9 100644 --- a/spring-cloud-function-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-function-context/src/main/resources/META-INF/spring.factories @@ -1,7 +1,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration,\ org.springframework.cloud.function.cloudevent.CloudEventsFunctionExtensionConfiguration,\ -org.springframework.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration +org.springframework.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration,\ +org.springframework.cloud.function.context.config.FunctionsEndpointAutoConfiguration org.springframework.cloud.function.context.WrapperDetector=\ org.springframework.cloud.function.context.config.FluxWrapperDetector org.springframework.context.ApplicationContextInitializer=\ diff --git a/spring-cloud-function-samples/function-sample/pom.xml b/spring-cloud-function-samples/function-sample/pom.xml index 8c391344b..a839518d8 100644 --- a/spring-cloud-function-samples/function-sample/pom.xml +++ b/spring-cloud-function-samples/function-sample/pom.xml @@ -26,6 +26,10 @@ + org.springframework.boot + spring-boot-starter-actuator + + org.springframework.cloud spring-cloud-starter-function-webflux diff --git a/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java b/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java index 0274d8c08..54f2e9dc6 100644 --- a/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java +++ b/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java @@ -24,13 +24,14 @@ import reactor.core.publisher.Flux; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.messaging.Message; // @checkstyle:off @SpringBootApplication public class SampleApplication { public static void main(String[] args) throws Exception { - SpringApplication.run(SampleApplication.class, args); + SpringApplication.run(SampleApplication.class, "--management.endpoints.web.exposure.include=functions"); } @Bean @@ -38,6 +39,11 @@ public class SampleApplication { return value -> value.toUpperCase(); } + @Bean + public Function, String> uppercaseMessage() { + return value -> value.getPayload().toUpperCase(); + } + @Bean public Function, Flux> lowercase() { return flux -> flux.map(value -> value.toLowerCase()); From 2a66862360bfcc52f97e8d8968b12e8539474812 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 19:06:18 +0100 Subject: [PATCH 27/41] GH-766 Polishing, check-styles, package name change --- .../function/{endpoint => actuator}/FunctionsEndpoint.java | 6 ++---- .../context/config/FunctionsEndpointAutoConfiguration.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) rename spring-cloud-function-context/src/main/java/org/springframework/cloud/function/{endpoint => actuator}/FunctionsEndpoint.java (93%) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java similarity index 93% rename from spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java rename to spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java index c27820e6e..69135d46c 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/endpoint/FunctionsEndpoint.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package org.springframework.cloud.function.endpoint; +package org.springframework.cloud.function.actuator; import java.util.LinkedHashMap; - import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -26,11 +25,10 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; -import org.springframework.cloud.function.json.JsonMapper; /** * - * Actuator endpoint to access {@link FunctionCatalog} + * Actuator endpoint to access {@link FunctionCatalog}. * * @author Oleg Zhurakousky * @since 3.2 diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java index 912f5f2b9..d86929863 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java @@ -21,8 +21,8 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.condition.Conditi import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.function.actuator.FunctionsEndpoint; import org.springframework.cloud.function.context.FunctionCatalog; -import org.springframework.cloud.function.endpoint.FunctionsEndpoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; From b46946a2188cb91121f233ed9c85c49db4169f8d Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 21:47:18 +0100 Subject: [PATCH 28/41] GH-766 Simplify type description display purposes --- .../function/actuator/FunctionsEndpoint.java | 19 +++++++++++++++---- .../java/com/example/SampleApplication.java | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java index 69135d46c..008a969db 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/actuator/FunctionsEndpoint.java @@ -24,6 +24,7 @@ import java.util.TreeMap; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.cloud.function.context.FunctionCatalog; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; /** @@ -51,20 +52,30 @@ public class FunctionsEndpoint { Map functionMap = new LinkedHashMap<>(); if (function.isFunction()) { functionMap.put("type", "FUNCTION"); - functionMap.put("input-type", function.getInputType().toString()); - functionMap.put("output-type", function.getOutputType().toString()); + functionMap.put("input-type", this.toSimplePolyIn(function)); + functionMap.put("output-type", this.toSimplePolyOut(function)); } else if (function.isConsumer()) { functionMap.put("type", "CONSUMER"); - functionMap.put("input-type", function.getInputType().toString()); + functionMap.put("input-type", this.toSimplePolyIn(function)); } else { functionMap.put("type", "SUPPLIER"); - functionMap.put("output-type", function.getOutputType().toString()); + functionMap.put("output-type", this.toSimplePolyOut(function)); } allFunctions.put(name, functionMap); } + + return allFunctions; } + + private String toSimplePolyOut(FunctionInvocationWrapper function) { + return FunctionTypeUtils.getRawType(function.getItemType(function.getOutputType())).getSimpleName().toLowerCase(); + } + + private String toSimplePolyIn(FunctionInvocationWrapper function) { + return FunctionTypeUtils.getRawType(function.getItemType(function.getInputType())).getSimpleName().toLowerCase(); + } } diff --git a/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java b/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java index 54f2e9dc6..4fb75cf8c 100644 --- a/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java +++ b/spring-cloud-function-samples/function-sample/src/main/java/com/example/SampleApplication.java @@ -40,8 +40,8 @@ public class SampleApplication { } @Bean - public Function, String> uppercaseMessage() { - return value -> value.getPayload().toUpperCase(); + public Function, Integer> uppercaseMessage() { + return value -> value.getPayload().toUpperCase().length(); } @Bean From 4076e889654c21c347d66dad6bb08f2449e56612 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 22:09:27 +0100 Subject: [PATCH 29/41] GH-768 Modified test to try to force the issue --- .../BeanFactoryAwareFunctionRegistryTests.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 71f343d44..6758351f3 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 @@ -651,8 +651,10 @@ public class BeanFactoryAwareFunctionRegistryTests { FunctionCatalog catalog = this.configureCatalog(SCF_GH_768ConfigurationAsFunction.class); Function function = catalog.lookup("echo"); - String result = (String) function.apply("{\"ricky\":{\"name\":\"ricky\"}}"); - assertThat(result).isEqualTo("{ricky=Person: ricky/0}"); + JsonMapper mapper = this.context.getBean(JsonMapper.class); + String date = mapper.toString(new Date()); + String result = (String) function.apply("{\"date\":" + date + "}"); + assertThat(result).startsWith("{date="); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1135,12 +1137,12 @@ public class BeanFactoryAwareFunctionRegistryTests { @EnableAutoConfiguration public static class SCF_GH_768ConfigurationAsFunction { @Bean - public Function, String> echoToString() { - return persons -> { - for (Entry entry : persons.entrySet()) { - assertThat(entry.getValue().getName()).isNotEmpty(); // would fail if value would not be converted to Person + public Function, String> echoToString() { + return data -> { + for (Entry dataEntry : data.entrySet()) { + assertThat(dataEntry.getValue()).isInstanceOf(Date.class); // would fail if value would not be converted to Person } - return persons.toString(); + return data.toString(); }; } } From 11d29fef2d1cb75fac7738c3dea8e26f58330675 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 1 Dec 2021 08:21:57 +0100 Subject: [PATCH 30/41] GH-766 Add initial docs for function visualization Resolves #766 --- docs/src/main/asciidoc/functional.adoc | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/docs/src/main/asciidoc/functional.adoc b/docs/src/main/asciidoc/functional.adoc index 2c57036be..0bbe7e1c1 100644 --- a/docs/src/main/asciidoc/functional.adoc +++ b/docs/src/main/asciidoc/functional.adoc @@ -123,6 +123,80 @@ for example, you will need to use `@EnableAutoConfiguration`. Your functions can declarations if you want (i.e. the "hybrid" style), but in that case you will need to explicitly switch off the "full functional mode" using `spring.functional.enabled=false` so that Spring Boot can take back control. +[[function_visualization]] += Function visualization and control + +Spring Cloud Function supports visualization of functions available in `FunctionCatalog` through Actuator endpoints as well as programmatic way. + +==== Programmatic way + +To see function available within your application context programmatically all you need is access to `FunctionCatalog`. There you can +finds methods to get the size of the catalog, lookup functions as well as list the names of all the available functions. + +For example, + +[source,java] +---- +FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class); +int size = functionCatalog.size(); // will tell you how many functions available in catalog +Set names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog +. . . +---- + +==== Actuator +Since actuator and web are optional, you must first add one of the web dependencies as well as add the actuator dependency manually. +The following example shows how to add the dependency for the Web framework: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-web + +---- + +The following example shows how to add the dependency for the WebFlux framework: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-webflux + +---- + +You can add the Actuator dependency as follows: +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-actuator + +---- + +You must also enable the `functions` actuator endpoints by setting the following property: `--management.endpoints.web.exposure.include=functions`. + +Access the following URL to see the functions in FunctionCatalog: +`http://:/actuator/functions` + +For example, +[source,text] +---- +curl http://localhost:8080/actuator/functions +---- + +Your output should look something like this: +[source,text] +---- +{"charCounter": + {"type":"FUNCTION","input-type":"string","output-type":"integer"}, + "logger": + {"type":"CONSUMER","input-type":"string"}, + "functionRouter": + {"type":"FUNCTION","input-type":"object","output-type":"object"}, + "words": + {"type":"SUPPLIER","output-type":"string"}. . . +---- = Testing Functional Applications From d1e1cdac368ffa4fb12d8c58f92734544cb595f1 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 29 Nov 2021 17:04:35 -0500 Subject: [PATCH 31/41] Updates broker to 0.3.0 Resolves #775 --- pom.xml | 8 --- spring-cloud-function-rsocket/pom.xml | 14 ++--- .../RSocketRoutingAutoConfiguration.java | 6 +-- .../function/rsocket/RoutingBrokerTests.java | 52 +++++++++---------- .../src/test/resources/application.properties | 4 +- 5 files changed, 37 insertions(+), 47 deletions(-) diff --git a/pom.xml b/pom.xml index 15bd9dbca..f6bfe5acc 100644 --- a/pom.xml +++ b/pom.xml @@ -172,14 +172,6 @@ - - rsocket-snapshots - RSocket Snapshots - https://oss.jfrog.org/oss-snapshot-local - - true - - spring-snapshots Spring Snapshots diff --git a/spring-cloud-function-rsocket/pom.xml b/spring-cloud-function-rsocket/pom.xml index 9f316057e..735c6adee 100644 --- a/spring-cloud-function-rsocket/pom.xml +++ b/spring-cloud-function-rsocket/pom.xml @@ -16,7 +16,7 @@ - 0.1.0 + 0.3.0 @@ -37,9 +37,9 @@ spring-cloud-function-context - io.rsocket.routing - rsocket-routing-client-spring - ${rsocket-routing.version} + io.rsocket.broker + rsocket-broker-client-spring + ${rsocket-broker.version} true @@ -58,9 +58,9 @@ test - io.rsocket.routing - rsocket-routing-broker-spring - ${rsocket-routing.version} + io.rsocket.broker + rsocket-broker-spring + ${rsocket-broker.version} test diff --git a/spring-cloud-function-rsocket/src/main/java/org/springframework/cloud/function/rsocket/RSocketRoutingAutoConfiguration.java b/spring-cloud-function-rsocket/src/main/java/org/springframework/cloud/function/rsocket/RSocketRoutingAutoConfiguration.java index 23b7e0c70..abf37dbd2 100644 --- a/spring-cloud-function-rsocket/src/main/java/org/springframework/cloud/function/rsocket/RSocketRoutingAutoConfiguration.java +++ b/spring-cloud-function-rsocket/src/main/java/org/springframework/cloud/function/rsocket/RSocketRoutingAutoConfiguration.java @@ -16,7 +16,7 @@ package org.springframework.cloud.function.rsocket; -import io.rsocket.routing.client.spring.RoutingClientAutoConfiguration; +import io.rsocket.broker.client.spring.BrokerClientAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -35,9 +35,9 @@ import org.springframework.messaging.rsocket.RSocketConnectorConfigurer; * @since 3.1 */ @Configuration(proxyBeanMethods = false) -@ConditionalOnClass(RoutingClientAutoConfiguration.class) +@ConditionalOnClass(BrokerClientAutoConfiguration.class) @ConditionalOnProperty(name = FunctionProperties.PREFIX + ".rsocket.enabled", matchIfMissing = true) -@AutoConfigureBefore(RoutingClientAutoConfiguration.class) +@AutoConfigureBefore(BrokerClientAutoConfiguration.class) @AutoConfigureAfter(RSocketAutoConfiguration.class) class RSocketRoutingAutoConfiguration { diff --git a/spring-cloud-function-rsocket/src/test/java/org/springframework/cloud/function/rsocket/RoutingBrokerTests.java b/spring-cloud-function-rsocket/src/test/java/org/springframework/cloud/function/rsocket/RoutingBrokerTests.java index dbf77cd10..595fa57ce 100644 --- a/spring-cloud-function-rsocket/src/test/java/org/springframework/cloud/function/rsocket/RoutingBrokerTests.java +++ b/spring-cloud-function-rsocket/src/test/java/org/springframework/cloud/function/rsocket/RoutingBrokerTests.java @@ -16,9 +16,10 @@ package org.springframework.cloud.function.rsocket; +import java.time.Duration; import java.util.function.Function; -import io.rsocket.routing.client.spring.RoutingMetadata; +import io.rsocket.broker.client.spring.BrokerMetadata; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -64,7 +65,7 @@ public class RoutingBrokerTests { public void testRoutingWithProperty() throws Exception { this.setup(true); RSocketRequester requester = clientContext.getBean(RSocketRequester.class); - // route(uppercase) used to find function, must match io.rsocket.routing.client.address entry + // route(uppercase) used to find function, must match io.rsocket.broker.client.address entry Mono result = requester.route("uppercase") // auto creates metadata .data("\"hello\"") @@ -74,14 +75,14 @@ public class RoutingBrokerTests { .create(result) .expectNext("HELLO") .expectComplete() - .verify(); + .verify(Duration.ofSeconds(15)); } @Test public void testRoutingWithMessage() throws Exception { this.setup(false); RSocketRequester requester = clientContext.getBean(RSocketRequester.class); - RoutingMetadata metadata = clientContext.getBean(RoutingMetadata.class); + BrokerMetadata metadata = clientContext.getBean(BrokerMetadata.class); Mono result = requester.route("uppercase") // used to find function .metadata(metadata.address("samplefn")) .data("\"hello\"") @@ -91,41 +92,40 @@ public class RoutingBrokerTests { .create(result) .expectNext("HELLO") .expectComplete() - .verify(); + .verify(Duration.ofSeconds(15)); } private void setup(boolean routingWithProperty) { - int routingBrokerProxyPort = SocketUtils.findAvailableTcpPort(); - int routingBrokerClusterPort = SocketUtils.findAvailableTcpPort(); + int brokerProxyPort = SocketUtils.findAvailableTcpPort(); + int brokerClusterPort = SocketUtils.findAvailableTcpPort(); // start broker brokerContext = new SpringApplicationBuilder(SimpleConfiguration.class).web(WebApplicationType.NONE).run( - "--logging.level.io.rsocket.routing.broker=TRACE", + "--logging.level.io.rsocket.broker=TRACE", "--spring.cloud.function.rsocket.enabled=false", - "--io.rsocket.routing.client.enabled=false", - "--io.rsocket.routing.broker.enabled=true", - "--io.rsocket.routing.broker.tcp.port=" + routingBrokerProxyPort, - "--io.rsocket.routing.broker.cluster.port=" + routingBrokerClusterPort); + "--io.rsocket.broker.client.enabled=false", + "--io.rsocket.broker.enabled=true", + "--io.rsocket.broker.uri=tcp://localhost:" + brokerProxyPort, + "--io.rsocket.broker.cluster.uri=tcp://localhost:" + brokerClusterPort); // start function connecting to broker, service-name=samplefn functionContext = new SpringApplicationBuilder(SampleFunctionConfiguration.class).web(WebApplicationType.NONE) .run("--logging.level.org.springframework.cloud.function=DEBUG", - "--io.rsocket.routing.client.enabled=true", - "--io.rsocket.routing.client.service-name=samplefn", - "--io.rsocket.routing.client.brokers[0].tcp.host=localhost", - "--io.rsocket.routing.client.brokers[0].tcp.port=" + routingBrokerProxyPort, - "--io.rsocket.routing.broker.enabled=false", + "--logging.level.io.rsocket.broker.client=TRACE", + "--io.rsocket.broker.client.enabled=true", + "--io.rsocket.broker.client.service-name=samplefn", + "--io.rsocket.broker.client.brokers[0]=tcp://localhost:" + brokerProxyPort, + "--io.rsocket.broker.enabled=false", "--spring.cloud.function.definition=uppercase"); // start testclient connecting to broker, for RSocketRequester clientContext = new SpringApplicationBuilder(SimpleConfiguration.class).web(WebApplicationType.NONE).run( - "--logging.level.io.rsocket.routing.client=TRACE", + "--logging.level.io.rsocket.broker.client=TRACE", "--spring.cloud.function.rsocket.enabled=false", - "--io.rsocket.routing.client.enabled=true", - "--io.rsocket.routing.client.service-name=testclient", - routingWithProperty ? "--io.rsocket.routing.client.address.uppercase.service_name=samplefn" : "", - "--io.rsocket.routing.client.brokers[0].tcp.host=localhost", - "--io.rsocket.routing.client.brokers[0].tcp.port=" + routingBrokerProxyPort, - "--io.rsocket.routing.broker.enabled=false"); + "--io.rsocket.broker.client.enabled=true", + "--io.rsocket.broker.client.service-name=testclient", + routingWithProperty ? "--io.rsocket.broker.client.address.uppercase.service_name=samplefn" : "", + "--io.rsocket.broker.client.brokers[0]=tcp://localhost:" + brokerProxyPort, + "--io.rsocket.broker.enabled=false"); } @@ -140,9 +140,7 @@ public class RoutingBrokerTests { public static class SampleFunctionConfiguration { @Bean public Function uppercase() { - return v -> { - return v.toUpperCase(); - }; + return v -> v.toUpperCase(); } } } diff --git a/spring-cloud-function-rsocket/src/test/resources/application.properties b/spring-cloud-function-rsocket/src/test/resources/application.properties index 7a1ee9c3e..83911652b 100644 --- a/spring-cloud-function-rsocket/src/test/resources/application.properties +++ b/spring-cloud-function-rsocket/src/test/resources/application.properties @@ -1,2 +1,2 @@ -io.rsocket.routing.broker.enabled=false -io.rsocket.routing.client.enabled=false +io.rsocket.broker.enabled=false +io.rsocket.broker.client.enabled=false From d1cbbe0d72a692ca424a55d8223a19f1ae94f272 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 1 Dec 2021 09:53:47 +0100 Subject: [PATCH 32/41] Update Kotlin version --- spring-cloud-function-context/pom.xml | 4 ++-- spring-cloud-function-kotlin/pom.xml | 4 ++-- .../function-sample-kotlin-web/pom.xml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spring-cloud-function-context/pom.xml b/spring-cloud-function-context/pom.xml index bbffa76d4..b25a2c006 100644 --- a/spring-cloud-function-context/pom.xml +++ b/spring-cloud-function-context/pom.xml @@ -127,7 +127,7 @@ kotlin-maven-plugin org.jetbrains.kotlin - 1.6.0-RC + 1.6.0 -Xjsr305=strict @@ -166,7 +166,7 @@ org.jetbrains.kotlin kotlin-maven-allopen - 1.6.0-RC + 1.6.0 diff --git a/spring-cloud-function-kotlin/pom.xml b/spring-cloud-function-kotlin/pom.xml index d7c524263..a3bebfda8 100644 --- a/spring-cloud-function-kotlin/pom.xml +++ b/spring-cloud-function-kotlin/pom.xml @@ -64,7 +64,7 @@ kotlin-maven-plugin org.jetbrains.kotlin - 1.6.0-RC + 1.6.0 -Xjsr305=strict @@ -93,7 +93,7 @@ org.jetbrains.kotlin kotlin-maven-allopen - 1.6.0-RC + 1.6.0 diff --git a/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml b/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml index 311a1cb6f..350bec73a 100644 --- a/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml +++ b/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml @@ -28,12 +28,12 @@ org.jetbrains.kotlin kotlin-reflect - 1.6.0-RC + 1.6.0 org.jetbrains.kotlin kotlin-stdlib-jdk8 - 1.6.0-RC + 1.6.0 org.springframework.cloud @@ -79,7 +79,7 @@ org.jetbrains.kotlin kotlin-maven-plugin - 1.6.0-RC + 1.6.0 -Xjsr305=strict From 99a49637d27b1dc15be944af9325a0b0d16ba3ff Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 1 Dec 2021 17:12:27 +0100 Subject: [PATCH 33/41] Fix bean name for FunctionsEndpoint --- .../context/config/FunctionsEndpointAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java index d86929863..861b50c5c 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/FunctionsEndpointAutoConfiguration.java @@ -39,7 +39,7 @@ public class FunctionsEndpointAutoConfiguration { @Bean @ConditionalOnAvailableEndpoint - public FunctionsEndpoint bindingsEndpoint(FunctionCatalog functionCatalog) { + public FunctionsEndpoint functionsEndpoint(FunctionCatalog functionCatalog) { return new FunctionsEndpoint(functionCatalog); } From d3324d273b3a66c31b2335f0f06742f8606c6d0b Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 6 Dec 2021 09:53:57 +0100 Subject: [PATCH 34/41] Cleanup in grpc docs --- .../spring-cloud-function-grpc/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/README.md b/spring-cloud-function-adapters/spring-cloud-function-grpc/README.md index 0f13b5dd2..57b6d7ce5 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/README.md +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/README.md @@ -26,24 +26,24 @@ In the server (default) mode, te gRPC server would be bound to te default port * At the center of gRPC and Spring Cloud Function integration is a canonical protobuff structure - `GrpcMessage`. It is modeled after Spring [Message](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/messaging/Message.html). ``` -message GrpcMessage { +message GrpcSpringMessage { bytes payload = 1; map headers = 2; } ``` As you can see it is a very generic structure which can support any type of data amd metadata you wish to exchange. -It alos defines a `MessagingService` allowing you to generate required stubs to support true plolyglot nature of gRPC. +It also defines a `MessagingService` allowing you to generate required stubs to support true plolyglot nature of gRPC. ``` service MessagingService { - rpc biStream(stream GrpcMessage) returns (stream GrpcMessage); + rpc biStream(stream GrpcSpringMessage) returns (stream GrpcSpringMessage); - rpc clientStream(stream GrpcMessage) returns (GrpcMessage); + rpc clientStream(stream GrpcSpringMessage) returns (GrpcSpringMessage); - rpc serverStream(GrpcMessage) returns (stream GrpcMessage); + rpc serverStream(GrpcSpringMessage) returns (stream GrpcSpringMessage); - rpc requestReply(GrpcMessage) returns (GrpcMessage); + rpc requestReply(GrpcSpringMessage) returns (GrpcSpringMessage); } ``` That said, when using Java, you do not need to generate anything, rather identify function definition and send and receive Spring `Messages`. From 93ecdb4c6f6ea123844c6a0294aa7107492f7f8a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 6 Dec 2021 12:55:42 +0100 Subject: [PATCH 35/41] Add additional logging to CustomRuntimeEventLoop --- .../adapter/aws/CustomRuntimeEventLoop.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java index 499297c64..83d9f256d 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java @@ -174,25 +174,33 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { private FunctionInvocationWrapper locateFunction(Environment environment, FunctionCatalog functionCatalog, MediaType contentType) { String handlerName = environment.getProperty("DEFAULT_HANDLER"); + if (logger.isDebugEnabled()) { + logger.debug("Value of DEFAULT_HANDLER env: " + handlerName); + } FunctionInvocationWrapper function = functionCatalog.lookup(handlerName, contentType.toString()); if (function == null) { + logger.debug("Could not locate function under DEFAULT_HANDLER"); handlerName = environment.getProperty("_HANDLER"); + if (logger.isDebugEnabled()) { + logger.debug("Value of _HANDLER env: " + handlerName); + } function = functionCatalog.lookup(handlerName, contentType.toString()); } if (function == null) { - function = functionCatalog.lookup(null, contentType.toString()); + logger.debug("Could not locate function under _HANDLER"); + function = functionCatalog.lookup((String) null, contentType.toString()); } if (function == null) { + logger.info("Could not determine default function"); handlerName = environment.getProperty("spring.cloud.function.definition"); + if (logger.isDebugEnabled()) { + logger.debug("Value of 'spring.cloud.function.definition' env: " + handlerName); + } function = functionCatalog.lookup(handlerName, contentType.toString()); } - if (function == null) { - function = functionCatalog.lookup(null, contentType.toString()); - } - Assert.notNull(function, "Failed to locate function. Tried locating default function, " + "function by 'DEFAULT_HANDLER', '_HANDLER' env variable as well as'spring.cloud.function.definition'. " + "Functions available in catalog are: " + functionCatalog.getNames(null)); From 2924777d346082f5715a10b96d89c6c490490674 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 7 Dec 2021 13:23:07 +0100 Subject: [PATCH 36/41] GH-780 Fix lifecycle of Kotlin function transformation Resolves #780 --- .../KotlinLambdaToFunctionAutoConfiguration.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java index e1e5b62df..f9dc48484 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java @@ -38,9 +38,9 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -91,11 +91,12 @@ public class KotlinLambdaToFunctionAutoConfiguration { * @return the bean factory post processor */ @Bean - public SmartInitializingSingleton kotlinToFunctionTransformer(ConfigurableListableBeanFactory beanFactory) { - return new SmartInitializingSingleton() { + public static BeanFactoryPostProcessor kotlinToFunctionTransformer(ConfigurableListableBeanFactory beanFactory) { + return new BeanFactoryPostProcessor() { @Override - public void afterSingletonsInstantiated() { + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName); @@ -112,11 +113,9 @@ public class KotlinLambdaToFunctionAutoConfiguration { } } } - }; } - @SuppressWarnings({ "unchecked", "rawtypes" }) public static final class KotlinFunctionWrapper implements Function, Supplier, Consumer, Function0, Function1, Function2, From dfc700e2c9cc09f5f62792de28a0fd9291ca1830 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 7 Dec 2021 15:02:40 +0100 Subject: [PATCH 37/41] Add support for returning Mono for gRPC requestReply Resolves #776 --- .../cloud/function/grpc/GrpcUtils.java | 9 ++- .../function/grpc/MessageHandlingHelper.java | 30 ++++++++-- .../function/grpc/GrpcInteractionTests.java | 55 +++++++++++++++++++ 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java index ce38c883e..7ed7d7117 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/GrpcUtils.java @@ -87,8 +87,13 @@ public final class GrpcUtils { MessagingServiceGrpc.MessagingServiceBlockingStub stub = MessagingServiceGrpc .newBlockingStub(channel); - GrpcSpringMessage response = stub.requestReply(toGrpcSpringMessage(inputMessage)); - return fromGrpcSpringMessage(response); + try { + GrpcSpringMessage response = stub.requestReply(toGrpcSpringMessage(inputMessage)); + return fromGrpcSpringMessage(response); + } + catch (Exception e) { + throw new IllegalStateException(e); + } } finally { channel.shutdownNow(); diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/MessageHandlingHelper.java b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/MessageHandlingHelper.java index caadb6e92..c24e3cd4f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/MessageHandlingHelper.java +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/main/java/org/springframework/cloud/function/grpc/MessageHandlingHelper.java @@ -24,12 +24,14 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import com.google.protobuf.ByteString; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Status; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import reactor.core.publisher.Sinks.Many; @@ -38,6 +40,7 @@ import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.FunctionProperties; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.context.SmartLifecycle; import org.springframework.messaging.Message; @@ -75,12 +78,29 @@ public class MessageHandlingHelper implements Smar public void requestReply(T request, StreamObserver responseObserver) { Message message = this.toSpringMessage(request); FunctionInvocationWrapper function = this.resolveFunction(message.getHeaders()); + if (FunctionTypeUtils.isFlux(function.getOutputType())) { + String errorMessage = "Flux reply is not supported for `requestReply` mode"; + responseObserver.onError(Status.UNKNOWN.withDescription(errorMessage) + .withCause(new UnsupportedOperationException(errorMessage)).asRuntimeException()); + return; + } - Message replyMessage = (Message) function.apply(message); - GeneratedMessageV3 reply = this.toGrpcMessage(replyMessage, (Class) request.getClass()); - - responseObserver.onNext((T) reply); - responseObserver.onCompleted(); + Object replyMessage = function.apply(message); + if (replyMessage instanceof Message) { + GeneratedMessageV3 reply = this.toGrpcMessage((Message) replyMessage, (Class) request.getClass()); + responseObserver.onNext((T) reply); + responseObserver.onCompleted(); + } + else if (replyMessage instanceof Publisher) { + if (replyMessage instanceof Mono) { + Mono.from((Publisher) replyMessage).doOnNext(reply -> { + GeneratedMessageV3 replyGrps = this.toGrpcMessage((Message) reply, (Class) request.getClass()); + responseObserver.onNext((T) replyGrps); + responseObserver.onCompleted(); + }) + .subscribe(); + } + } } @SuppressWarnings("unchecked") diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java index 6d39125a1..83015c8e8 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/src/test/java/org/springframework/cloud/function/grpc/GrpcInteractionTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -40,6 +41,7 @@ import org.springframework.util.MimeTypeUtils; import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; /** * @@ -79,6 +81,49 @@ public class GrpcInteractionTests { } } + @Test + public void testRequestReplyWithMonoReturn() { + int port = SocketUtils.findAvailableTcpPort(); + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + SampleConfiguration.class).web(WebApplicationType.NONE).run( + "--spring.jmx.enabled=false", + "--spring.cloud.function.definition=uppercaseMonoReturn", + "--spring.cloud.function.grpc.port=" + port)) { + + Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes()) + .setHeader("foo", "bar") + .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) + .build(); + + Message reply = GrpcUtils.requestReply("localhost", port, message); + + assertThat(reply.getPayload()).isEqualTo("\"HELLO GRPC\"".getBytes()); + } + } + + @Test + public void testRequestReplyWithFluxReturn() { + int port = SocketUtils.findAvailableTcpPort(); + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + SampleConfiguration.class).web(WebApplicationType.NONE).run( + "--spring.jmx.enabled=false", + "--spring.cloud.function.definition=uppercaseFluxReturn", + "--spring.cloud.function.grpc.port=" + port)) { + + Message message = MessageBuilder.withPayload("\"hello gRPC\"".getBytes()) + .setHeader("foo", "bar") + .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) + .build(); + try { + GrpcUtils.requestReply("localhost", port, message); + fail(); + } + catch (Exception e) { + assertThat(e.getMessage()).contains("Flux reply is not supported for `requestReply` mode"); + } + } + } + @Test public void testRequstReplyFunctionDefinitionInMessage() { int port = SocketUtils.findAvailableTcpPort(); @@ -263,6 +308,16 @@ public class GrpcInteractionTests { return v -> v.toUpperCase(); } + @Bean + public Function> uppercaseMonoReturn() { + return v -> Mono.just(v.toUpperCase()); + } + + @Bean + public Function> uppercaseFluxReturn() { + return v -> Flux.just(v.toUpperCase(), v.toUpperCase() + "-1", v.toUpperCase() + "-2"); + } + @Bean public Function reverse() { return v -> new StringBuilder(v).reverse().toString(); From fe060f6a6c2c2d5f2f87037479cfdd52a8965d8f Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 3 Jan 2022 14:42:31 +0100 Subject: [PATCH 38/41] GH-786 Fix regression with RoutingFunction over AWS APIGateway Resolves #786 --- .../function/adapter/aws/FunctionInvoker.java | 35 +++++++++++++------ .../context/config/RoutingFunction.java | 2 +- .../function-sample-aws-routing/README.adoc | 15 ++++---- .../function-sample-aws-routing/pom.xml | 4 +++ 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java index ef3e79775..d8e31deeb 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java @@ -82,7 +82,7 @@ public class FunctionInvoker implements RequestStreamHandler { this.start(); } - @SuppressWarnings("rawtypes") + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { final byte[] payload = StreamUtils.copyToByteArray(input); @@ -99,10 +99,18 @@ public class FunctionInvoker implements RequestStreamHandler { // TODO we should eventually completely delegate to message converter - //Message requestMessage = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build(); - Message requestMessage = isApiGateway - ? MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build() - : AWSLambdaUtils.generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); + Message requestMessage; + if (isApiGateway) { + MessageBuilder builder = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true); + if (structMessage instanceof Map && ((Map) structMessage).containsKey("headers")) { + builder.copyHeaders((Map) ((Map) structMessage).get("headers")); + } + requestMessage = builder.build(); + } + else { + requestMessage = AWSLambdaUtils + .generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); + } try { Object response = this.function.apply(requestMessage); @@ -111,15 +119,20 @@ public class FunctionInvoker implements RequestStreamHandler { } catch (Exception e) { logger.error(e); - StreamUtils.copy(this.buildExceptionResult(requestMessage, e), output); + StreamUtils.copy(this.buildExceptionResult(requestMessage, e, isApiGateway), output); } } - private byte[] buildExceptionResult(Message requestMessage, Exception exception) throws IOException { - APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); - event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); - event.setBody(exception.getMessage()); - return this.jsonMapper.toJson(event); + private byte[] buildExceptionResult(Message requestMessage, Exception exception, boolean isApiGateway) throws IOException { + if (isApiGateway) { + APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); + event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); + event.setBody(exception.getMessage()); + return this.jsonMapper.toJson(event); + } + else { + throw new IllegalStateException(exception); + } } @SuppressWarnings("unchecked") diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java index 63445f1af..dd6b965b3 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java @@ -137,7 +137,7 @@ public class RoutingFunction implements Function { else { throw new IllegalStateException("Failed to establish route, since neither were provided: " + "'spring.cloud.function.definition' as Message header or as application property or " - + "'spring.cloud.function.routing-expression' as application property."); + + "'spring.cloud.function.routing-expression' as application property. Incoming message: " + input); } } } diff --git a/spring-cloud-function-samples/function-sample-aws-routing/README.adoc b/spring-cloud-function-samples/function-sample-aws-routing/README.adoc index 181929a3e..6d0c545a7 100644 --- a/spring-cloud-function-samples/function-sample-aws-routing/README.adoc +++ b/spring-cloud-function-samples/function-sample-aws-routing/README.adoc @@ -10,18 +10,17 @@ You can do so in two different ways. 1. You can provide `spring_cloud_function_definition` environment variable setting its value to the desired function definition, which could also be composition (e.g., `spring_cloud_function_definition=foo|bar`). -2. A more dynamic and recommended approach would be to fallback on auto routing capabilities of spring-cloud function's in AWS environment. -Basically every time you have more then one function in your configuration, the framework will bind -[Routing Function](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) -as AWS Lambda, and all you need to to is provide a routing instruction via Message headers or environment variables. The instructions could themselves be very dynamic, -since we support both SpEL and registering a callback interface. For more details on routing mechanisms please refer to -[Function Routing and Filtering](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) section. - - NOTE: Keep in mind though that since AWS does not allow dots `.` and/or hyphens`-` in the name of the environment variable, you can benefit from boot support and simply substitute dots with underscores and hyphens with camel case. So for example `spring.cloud.function.definition` becomes `spring_cloud_function_definition` and `spring.cloud.function.routing-expression` becomes `spring_cloud_function_routingExpression`. +2. A more dynamic and recommended approach would be to fallback on auto routing capabilities of spring-cloud function's in AWS environment. +Basically every time you have more then one function in your configuration, the framework will bind +[Routing Function](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) +as AWS Lambda, and all you need to to is provide a routing instruction via Message headers or environment variables. The instructions could themselves be very dynamic, since we support both SpEL and registering a callback interface. For more details on routing mechanisms please refer to +[Function Routing and Filtering](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) section. + + In this example we have configuration with two functions; `uppercase` and `reverse`. When executing from AWS Lambda functions dashboard you can simply provide one of the mentioned properties as environment variables via Configuration tab. For example, you can set `spring_cloud_function_routingExpression` environment variable with the value of literal; SpEL expression `'uppercase'` (not the single quotes). diff --git a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml index 57a9d0348..f76a50462 100644 --- a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml @@ -33,6 +33,10 @@ org.springframework.cloud spring-cloud-function-adapter-aws + + org.springframework.boot + spring-boot-starter-web + com.amazonaws From 1bdcddef1ecbc382bdc7d5959ecebc187005f6b5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 11 Jan 2022 11:42:02 +0100 Subject: [PATCH 39/41] GH-788 Fix aws custom runtime samples Add spring web dependencies Resolves #788 --- .../function-sample-aws-custom-bean/pom.xml | 21 ++++--------------- .../function-sample-aws-custom/pom.xml | 16 +++++--------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml b/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml index 7719ca050..7075db0ac 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml @@ -39,23 +39,15 @@ org.slf4j slf4j-jdk14 - + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-test test - - - - - - - - - - - @@ -74,11 +66,6 @@ maven-surefire-plugin - - - - - org.apache.maven.plugins diff --git a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml index 6cf0ac116..54cf164e0 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml @@ -25,25 +25,19 @@ org.springframework.cloud spring-cloud-function-adapter-aws - - - - org.springframework.cloud spring-cloud-function-web - + test org.springframework.boot spring-boot-starter-web - - - - org.springframework - spring-web - + + + + org.springframework.boot spring-boot-starter From b4293878b41c3f24baa139b552486c7bd7ac23ce Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 12 Jan 2022 10:44:29 +0100 Subject: [PATCH 40/41] GH-789 Propagate AWS FunctionInvoker exceptions They will be handled by the AWS runtime and properly reported and recorded. Resolves #789 --- .../function/adapter/aws/FunctionInvoker.java | 27 +++---------------- .../adapter/aws/FunctionInvokerTests.java | 12 ++++++--- .../function-sample-aws/pom.xml | 4 +++ .../java/example/FunctionConfiguration.java | 2 +- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java index d8e31deeb..071a2c6a4 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java @@ -30,7 +30,6 @@ import java.util.Set; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -53,7 +52,6 @@ import org.springframework.cloud.function.utils.FunctionClassUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; -import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -112,27 +110,10 @@ public class FunctionInvoker implements RequestStreamHandler { .generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); } - try { - Object response = this.function.apply(requestMessage); - byte[] responseBytes = this.buildResult(requestMessage, response); - StreamUtils.copy(responseBytes, output); - } - catch (Exception e) { - logger.error(e); - StreamUtils.copy(this.buildExceptionResult(requestMessage, e, isApiGateway), output); - } - } - - private byte[] buildExceptionResult(Message requestMessage, Exception exception, boolean isApiGateway) throws IOException { - if (isApiGateway) { - APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); - event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); - event.setBody(exception.getMessage()); - return this.jsonMapper.toJson(event); - } - else { - throw new IllegalStateException(exception); - } + Object response = this.function.apply(requestMessage); + byte[] responseBytes = this.buildResult(requestMessage, response); + StreamUtils.copy(responseBytes, output); + // any exception should propagate } @SuppressWarnings("unchecked") diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/FunctionInvokerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/FunctionInvokerTests.java index a79230fd4..3b34e8b16 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/FunctionInvokerTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/FunctionInvokerTests.java @@ -49,6 +49,7 @@ import org.springframework.messaging.converter.AbstractMessageConverter; import org.springframework.util.MimeType; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; /** * @@ -914,9 +915,14 @@ public class FunctionInvokerTests { InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes()); ByteArrayOutputStream output = new ByteArrayOutputStream(); - invoker.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toByteArray(), Map.class); - assertThat(((String) result.get("body"))).startsWith("Failed to establish route, since neither were provided:"); + + try { + invoker.handleRequest(targetStream, output, null); + fail(); + } + catch (Exception e) { + // TODO: handle exception + } } @SuppressWarnings("rawtypes") diff --git a/spring-cloud-function-samples/function-sample-aws/pom.xml b/spring-cloud-function-samples/function-sample-aws/pom.xml index 708be331d..6a9020757 100644 --- a/spring-cloud-function-samples/function-sample-aws/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws/pom.xml @@ -37,6 +37,10 @@ org.springframework.cloud spring-cloud-function-web + + org.springframework.boot + spring-boot-starter-web + com.amazonaws diff --git a/spring-cloud-function-samples/function-sample-aws/src/main/java/example/FunctionConfiguration.java b/spring-cloud-function-samples/function-sample-aws/src/main/java/example/FunctionConfiguration.java index f44747302..ade13c608 100644 --- a/spring-cloud-function-samples/function-sample-aws/src/main/java/example/FunctionConfiguration.java +++ b/spring-cloud-function-samples/function-sample-aws/src/main/java/example/FunctionConfiguration.java @@ -21,7 +21,7 @@ public class FunctionConfiguration { public Function uppercase() { return value -> { if (value.equals("exception")) { - throw new RuntimeException("Intentional exception which should result in HTTP 417"); + throw new RuntimeException("Intentional exception"); } else { return value.toUpperCase(); From 5074b7eee403ac9f294256ea647288625277c3d1 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 14 Jan 2022 11:05:58 +0100 Subject: [PATCH 41/41] Finish migration to java17, boot 3.0, spring 6 --- docs/pom.xml | 2 +- pom.xml | 38 ++++++---- spring-cloud-function-adapters/pom.xml | 2 +- .../spring-cloud-function-adapter-aws/pom.xml | 3 +- .../pom.xml | 2 +- .../spring-cloud-function-adapter-gcp/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../spring-cloud-function-grpc/pom.xml | 2 +- spring-cloud-function-compiler/pom.xml | 2 +- spring-cloud-function-context/pom.xml | 12 ++- ...FactoryAwarePojoFunctionRegistryTests.java | 2 +- .../catalog/SimpleFunctionRegistryTests.java | 2 +- spring-cloud-function-core/pom.xml | 2 +- spring-cloud-function-dependencies/pom.xml | 4 +- spring-cloud-function-deployer/pom.xml | 2 +- .../src/it/bootapp-multi/pom.xml | 2 +- .../src/it/bootapp-with-javax/pom.xml | 2 +- .../src/it/bootapp-with-scf/pom.xml | 2 +- .../src/it/bootapp/pom.xml | 2 +- .../src/it/bootjar-multi/pom.xml | 2 +- .../src/it/bootjar/pom.xml | 2 +- .../src/it/bootjarnostart/pom.xml | 2 +- spring-cloud-function-kotlin/pom.xml | 2 +- spring-cloud-function-rsocket/pom.xml | 2 +- .../function-functional-sample-aws/pom.xml | 7 +- .../function-sample-aws-custom-bean/pom.xml | 73 ++++++++++--------- .../function-sample-aws-custom/pom.xml | 47 ++---------- .../function-sample-aws-routing/pom.xml | 7 +- .../function-sample-aws/pom.xml | 7 +- .../function-sample-azure/pom.xml | 7 +- .../pom.xml | 8 +- .../function-sample-cloudevent-stream/pom.xml | 13 ++-- .../function-sample-cloudevent/pom.xml | 5 +- .../function-sample-compiler/pom.xml | 2 +- .../pom.xml | 7 +- .../function-sample-gcp-background/pom.xml | 6 +- .../function-sample-gcp-http/pom.xml | 7 +- .../function-sample-grpc-cloudevent/pom.xml | 7 +- .../function-sample-kotlin-web/pom.xml | 55 ++++++++++---- .../function-sample-pof/pom.xml | 7 +- .../function-sample-pojo/pom.xml | 7 +- .../pom.xml | 2 +- .../function-sample-supplier-exporter/pom.xml | 41 ++++++++++- .../function-sample-task/pom.xml | 2 +- .../function-sample/pom.xml | 7 +- spring-cloud-function-samples/pom.xml | 2 +- spring-cloud-function-task/pom.xml | 2 +- spring-cloud-function-web/pom.xml | 12 +-- .../web/mvc/FunctionHandlerMapping.java | 4 +- spring-cloud-starter-function-web/pom.xml | 2 +- spring-cloud-starter-function-webflux/pom.xml | 2 +- 52 files changed, 242 insertions(+), 205 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index d27a69d5a..60527ff37 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT pom Spring Cloud Function Docs diff --git a/pom.xml b/pom.xml index f6bfe5acc..ba2b87556 100644 --- a/pom.xml +++ b/pom.xml @@ -6,18 +6,18 @@ spring-cloud-function-parent Spring Cloud Function Parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT pom org.springframework.cloud spring-cloud-build - 3.1.0-SNAPSHOT + 4.0.0-SNAPSHOT - 1.8 + 17 ${java.version} ${java.version} 1.0.27.RELEASE @@ -48,8 +48,16 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens java.base/java.util=ALL-UNNAMED + + + org.codehaus.mojo @@ -129,18 +137,18 @@ - - java11+ - - [11,) - - - - javax.annotation - javax.annotation-api - - - + + + + + + + + + + + + core diff --git a/spring-cloud-function-adapters/pom.xml b/spring-cloud-function-adapters/pom.xml index aad724969..e78de8ad4 100644 --- a/spring-cloud-function-adapters/pom.xml +++ b/spring-cloud-function-adapters/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-function-adapter-parent 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 5d6b09cdb..7c47ab30a 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 @@ -13,13 +13,12 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 3.9.0 1.12.29 1.0.3 diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/pom.xml index 6e1edb689..fed34cf64 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml index 74b81f066..091a93738 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml @@ -11,7 +11,7 @@ spring-cloud-function-adapter-parent org.springframework.cloud - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/pom.xml index 4dfd38fc2..149ca38b5 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml index 20fe0204d..9853012bb 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc-cloudevent-ext/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-function-grpc-cloudevent-ext spring-cloud-function-grpc-cloudevent-ext diff --git a/spring-cloud-function-adapters/spring-cloud-function-grpc/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-grpc/pom.xml index df7476029..bec41efae 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-grpc/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-grpc/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-compiler/pom.xml b/spring-cloud-function-compiler/pom.xml index 08bdf7707..3592c6a23 100644 --- a/spring-cloud-function-compiler/pom.xml +++ b/spring-cloud-function-compiler/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-context/pom.xml b/spring-cloud-function-context/pom.xml index b25a2c006..fb79aec29 100644 --- a/spring-cloud-function-context/pom.xml +++ b/spring-cloud-function-context/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT @@ -47,6 +47,16 @@ jackson-module-kotlin true + + javax.annotation + javax.annotation-api + 1.3.2 + + + javax.activation + javax.activation-api + 1.2.0 + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwarePojoFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwarePojoFunctionRegistryTests.java index 3f0910bb2..accc4c5ba 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwarePojoFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwarePojoFunctionRegistryTests.java @@ -133,7 +133,7 @@ public class BeanFactoryAwarePojoFunctionRegistryTests { } // POJO Function - private static class MyFunctionLike { + public static class MyFunctionLike { public String uppercase(String value) { return value.toUpperCase(); } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java index 3c2f04e49..c570c33e8 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistryTests.java @@ -135,7 +135,7 @@ public class SimpleFunctionRegistryTests { }; FunctionRegistration, String>> registration = new FunctionRegistration<>( - function, "echo").type(FunctionType.of(functionType)); + function, "echo").type(functionType); SimpleFunctionRegistry catalog = new SimpleFunctionRegistry(this.conversionService, this.messageConverter, new JacksonMapper(new ObjectMapper())); catalog.register(registration); diff --git a/spring-cloud-function-core/pom.xml b/spring-cloud-function-core/pom.xml index afa411c78..14a51730a 100644 --- a/spring-cloud-function-core/pom.xml +++ b/spring-cloud-function-core/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-dependencies/pom.xml b/spring-cloud-function-dependencies/pom.xml index ea2cc3035..9dd103699 100644 --- a/spring-cloud-function-dependencies/pom.xml +++ b/spring-cloud-function-dependencies/pom.xml @@ -6,11 +6,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 3.1.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-function-dependencies - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT pom Spring Cloud Function Dependencies Spring Cloud Function Dependencies diff --git a/spring-cloud-function-deployer/pom.xml b/spring-cloud-function-deployer/pom.xml index 0db33f47a..d3dc88e64 100644 --- a/spring-cloud-function-deployer/pom.xml +++ b/spring-cloud-function-deployer/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-deployer/src/it/bootapp-multi/pom.xml b/spring-cloud-function-deployer/src/it/bootapp-multi/pom.xml index b71aaac3c..88e73bd0d 100644 --- a/spring-cloud-function-deployer/src/it/bootapp-multi/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootapp-multi/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootapp-with-javax/pom.xml b/spring-cloud-function-deployer/src/it/bootapp-with-javax/pom.xml index 721ca3c4e..62d18fe1e 100644 --- a/spring-cloud-function-deployer/src/it/bootapp-with-javax/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootapp-with-javax/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootapp-with-scf/pom.xml b/spring-cloud-function-deployer/src/it/bootapp-with-scf/pom.xml index 0b6f7a08d..bd65190f0 100644 --- a/spring-cloud-function-deployer/src/it/bootapp-with-scf/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootapp-with-scf/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootapp/pom.xml b/spring-cloud-function-deployer/src/it/bootapp/pom.xml index dc7180ae6..ef70f9f2c 100644 --- a/spring-cloud-function-deployer/src/it/bootapp/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootapp/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootjar-multi/pom.xml b/spring-cloud-function-deployer/src/it/bootjar-multi/pom.xml index a72e39a73..24d7e6d5a 100644 --- a/spring-cloud-function-deployer/src/it/bootjar-multi/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootjar-multi/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootjar/pom.xml b/spring-cloud-function-deployer/src/it/bootjar/pom.xml index 6a945ae75..fd35734b2 100644 --- a/spring-cloud-function-deployer/src/it/bootjar/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootjar/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-deployer/src/it/bootjarnostart/pom.xml b/spring-cloud-function-deployer/src/it/bootjarnostart/pom.xml index d065723b6..d8de56115 100644 --- a/spring-cloud-function-deployer/src/it/bootjarnostart/pom.xml +++ b/spring-cloud-function-deployer/src/it/bootjarnostart/pom.xml @@ -18,7 +18,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-kotlin/pom.xml b/spring-cloud-function-kotlin/pom.xml index a3bebfda8..c26714810 100644 --- a/spring-cloud-function-kotlin/pom.xml +++ b/spring-cloud-function-kotlin/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-rsocket/pom.xml b/spring-cloud-function-rsocket/pom.xml index 735c6adee..4ba1ae743 100644 --- a/spring-cloud-function-rsocket/pom.xml +++ b/spring-cloud-function-rsocket/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-functional-sample-aws/pom.xml b/spring-cloud-function-samples/function-functional-sample-aws/pom.xml index 5cea3c211..ab4488ff9 100644 --- a/spring-cloud-function-samples/function-functional-sample-aws/pom.xml +++ b/spring-cloud-function-samples/function-functional-sample-aws/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-functional-sample-aws - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-functional-sample-aws @@ -15,17 +15,16 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 1.0.27.RELEASE 3.9.0 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml b/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml index 7075db0ac..277123c90 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-custom-bean/pom.xml @@ -5,19 +5,18 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT io.spring.sample function-sample-aws-custom-bean - 3.0.0.RELEASE + 4.0.0.RELEASE AWS Custom Runtime - @Bean sample Demo project for Spring Cloud Function with custom AWS Lambda runtime using @Bean style - 1.8 1.0.27.RELEASE - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT @@ -28,7 +27,7 @@ --> com.amazonaws aws-lambda-java-events - 2.2.6 + 3.9.0 @@ -106,34 +105,38 @@ - - - integration - - - - maven-surefire-plugin - 2.22.0 - - - integration-test - - test - - - - none - - - com/example/ContainerTests.java - - - - - - - - - - + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/release + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + diff --git a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml index 54cf164e0..0db7d4a4a 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml @@ -5,19 +5,18 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT io.spring.sample function-sample-aws-custom - 3.0.0.RELEASE + 4.0.0.RELEASE function-sample-aws-custom Demo project for Spring Cloud Function with custom AWS Lambda runtime - 1.8 1.0.27.RELEASE - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT @@ -34,39 +33,21 @@ org.springframework.boot spring-boot-starter-web - - - - + + jakarta.servlet + jakarta.servlet-api + provided + org.springframework.boot spring-boot-starter - - - org.springframework.boot spring-boot-starter-test test - - - - - - - - - - - - - - - - @@ -167,28 +148,16 @@ spring-snapshots Spring Snapshots https://repo.spring.io/libs-snapshot-local - - true - - - false - spring-milestones Spring Milestones https://repo.spring.io/libs-milestone-local - - false - spring-releases Spring Releases https://repo.spring.io/release - - false - diff --git a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml index f76a50462..aa8bb8b1e 100644 --- a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-aws-routing - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-aws-routing @@ -15,17 +15,16 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 1.0.27.RELEASE 2.0.2 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-aws/pom.xml b/spring-cloud-function-samples/function-sample-aws/pom.xml index 6a9020757..b9bd8a07b 100644 --- a/spring-cloud-function-samples/function-sample-aws/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-aws - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-aws @@ -15,17 +15,16 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 1.0.27.RELEASE 3.9.0 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-azure/pom.xml b/spring-cloud-function-samples/function-sample-azure/pom.xml index dcbd960a7..5d49dcc99 100644 --- a/spring-cloud-function-samples/function-sample-azure/pom.xml +++ b/spring-cloud-function-samples/function-sample-azure/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-azure - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-azure @@ -14,12 +14,11 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 function-sample-azure westus java-function-group @@ -54,7 +53,7 @@ org.springframework.cloud spring-cloud-function-dependencies - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT pom import diff --git a/spring-cloud-function-samples/function-sample-cloudevent-rsocket/pom.xml b/spring-cloud-function-samples/function-sample-cloudevent-rsocket/pom.xml index 760ed60bd..67ba27e59 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent-rsocket/pom.xml +++ b/spring-cloud-function-samples/function-sample-cloudevent-rsocket/pom.xml @@ -12,13 +12,12 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE @@ -28,7 +27,6 @@ org.springframework.cloud spring-cloud-function-rsocket - 3.1.0-SNAPSHOT @@ -37,7 +35,7 @@ org.springframework.cloud spring-cloud-stream-binder-kafka - 3.1.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-cloudevent-stream/pom.xml b/spring-cloud-function-samples/function-sample-cloudevent-stream/pom.xml index 3da862c23..5eb646eb9 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent-stream/pom.xml +++ b/spring-cloud-function-samples/function-sample-cloudevent-stream/pom.xml @@ -4,20 +4,19 @@ 4.0.0 io.spring.sample function-sample-cloudevent-stream - 0.0.1-SNAPSHOT + 4.0.0-RELEASE function-sample-cloudevent-stream Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE @@ -29,13 +28,13 @@ org.springframework.cloud spring-cloud-stream - 3.2.0-M2 + 4.0.0-SNAPSHOT org.springframework.cloud spring-cloud-stream-binder-rabbit - 3.2.0-M2 + 4.0.0-SNAPSHOT @@ -43,7 +42,7 @@ org.springframework.cloud spring-cloud-stream-binder-kafka - 3.2.0-M2 + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-cloudevent/pom.xml b/spring-cloud-function-samples/function-sample-cloudevent/pom.xml index b172d1885..834e4aa9e 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent/pom.xml +++ b/spring-cloud-function-samples/function-sample-cloudevent/pom.xml @@ -11,13 +11,12 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-samples/function-sample-compiler/pom.xml b/spring-cloud-function-samples/function-sample-compiler/pom.xml index dcf1e3f38..94ba570db 100644 --- a/spring-cloud-function-samples/function-sample-compiler/pom.xml +++ b/spring-cloud-function-samples/function-sample-compiler/pom.xml @@ -20,7 +20,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 3.1.2.RELEASE 1.0.17.RELEASE diff --git a/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml b/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml index be0aab22c..171f4cc3b 100644 --- a/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml +++ b/spring-cloud-function-samples/function-sample-functional-aws-routing/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-functional-aws-routing - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-functional-aws-routing @@ -15,17 +15,16 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 1.0.27.RELEASE 2.0.2 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-gcp-background/pom.xml b/spring-cloud-function-samples/function-sample-gcp-background/pom.xml index 8d569cd20..1fa44627a 100644 --- a/spring-cloud-function-samples/function-sample-gcp-background/pom.xml +++ b/spring-cloud-function-samples/function-sample-gcp-background/pom.xml @@ -7,7 +7,7 @@ io.spring.sample function-sample-gcp-background - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-gcp-background @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT @@ -23,7 +23,7 @@ org.springframework.cloud spring-cloud-function-adapter-gcp - 3.1.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-gcp-http/pom.xml b/spring-cloud-function-samples/function-sample-gcp-http/pom.xml index da1a9d1fc..e3a92d915 100644 --- a/spring-cloud-function-samples/function-sample-gcp-http/pom.xml +++ b/spring-cloud-function-samples/function-sample-gcp-http/pom.xml @@ -7,7 +7,7 @@ io.spring.sample function-sample-gcp-http - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-gcp-http @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT @@ -23,7 +23,7 @@ org.springframework.cloud spring-cloud-function-adapter-gcp - 3.1.3-SNAPSHOT + 4.0.0-SNAPSHOT @@ -59,7 +59,6 @@ maven-deploy-plugin - 2.8.2 true diff --git a/spring-cloud-function-samples/function-sample-grpc-cloudevent/pom.xml b/spring-cloud-function-samples/function-sample-grpc-cloudevent/pom.xml index 39715f0aa..ca001c973 100644 --- a/spring-cloud-function-samples/function-sample-grpc-cloudevent/pom.xml +++ b/spring-cloud-function-samples/function-sample-grpc-cloudevent/pom.xml @@ -7,17 +7,16 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT com.example.grpc function-sample-grpc-cloudevent - 0.0.1-RELEASE + 4.0.0-RELEASE function-sample-grpc-cloudevent Demo project for Spring Boot - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml b/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml index 350bec73a..3b081c326 100644 --- a/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml +++ b/spring-cloud-function-samples/function-sample-kotlin-web/pom.xml @@ -4,22 +4,17 @@ 4.0.0 io.spring.sample function-sample-kotlin-web - 1.0 + 4.0.0 function-sample-kotlin-web Demo project for Spring Cloud Function Web Kotlin integration org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - - 1.8 - 1.4.21 - - org.springframework.boot @@ -28,27 +23,25 @@ org.jetbrains.kotlin kotlin-reflect - 1.6.0 org.jetbrains.kotlin kotlin-stdlib-jdk8 - 1.6.0 org.springframework.cloud spring-cloud-function-kotlin - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT org.springframework.cloud spring-cloud-function-web - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT org.springframework.cloud spring-cloud-function-context - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT org.springframework.boot @@ -79,7 +72,6 @@ org.jetbrains.kotlin kotlin-maven-plugin - 1.6.0 -Xjsr305=strict @@ -92,11 +84,46 @@ org.jetbrains.kotlin kotlin-maven-allopen - ${kotlin.version} + 1.4.21 + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/release + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + diff --git a/spring-cloud-function-samples/function-sample-pof/pom.xml b/spring-cloud-function-samples/function-sample-pof/pom.xml index d90cf31fe..1c37adfda 100644 --- a/spring-cloud-function-samples/function-sample-pof/pom.xml +++ b/spring-cloud-function-samples/function-sample-pof/pom.xml @@ -5,7 +5,7 @@ io.spring.sample function-sample-pof - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-pof Spring Cloud Function Web Support @@ -13,16 +13,15 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT UTF-8 UTF-8 - 1.8 3.1.2.RELEASE - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-pojo/pom.xml b/spring-cloud-function-samples/function-sample-pojo/pom.xml index 6a6ea84a9..af9744684 100644 --- a/spring-cloud-function-samples/function-sample-pojo/pom.xml +++ b/spring-cloud-function-samples/function-sample-pojo/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-pojo - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-pojo Spring Cloud Function Web Support @@ -14,13 +14,12 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-samples/function-sample-spring-integration/pom.xml b/spring-cloud-function-samples/function-sample-spring-integration/pom.xml index 4161ebea2..8cc51067b 100644 --- a/spring-cloud-function-samples/function-sample-spring-integration/pom.xml +++ b/spring-cloud-function-samples/function-sample-spring-integration/pom.xml @@ -20,7 +20,7 @@ UTF-8 UTF-8 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-sample-supplier-exporter/pom.xml b/spring-cloud-function-samples/function-sample-supplier-exporter/pom.xml index eabf7d2fb..88053396f 100644 --- a/spring-cloud-function-samples/function-sample-supplier-exporter/pom.xml +++ b/spring-cloud-function-samples/function-sample-supplier-exporter/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample-aws-supplier-exporter - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample-aws-supplier-exporter Spring Cloud Function Example showing Supplier Exporter @@ -14,13 +14,12 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT @@ -119,4 +118,38 @@ + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/release + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + diff --git a/spring-cloud-function-samples/function-sample-task/pom.xml b/spring-cloud-function-samples/function-sample-task/pom.xml index c53badfb7..fe24b7221 100644 --- a/spring-cloud-function-samples/function-sample-task/pom.xml +++ b/spring-cloud-function-samples/function-sample-task/pom.xml @@ -20,7 +20,7 @@ 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.10.RELEASE 3.1.2.RELEASE diff --git a/spring-cloud-function-samples/function-sample/pom.xml b/spring-cloud-function-samples/function-sample/pom.xml index a839518d8..b7b68a826 100644 --- a/spring-cloud-function-samples/function-sample/pom.xml +++ b/spring-cloud-function-samples/function-sample/pom.xml @@ -6,7 +6,7 @@ io.spring.sample function-sample - 2.0.0.RELEASE + 4.0.0.RELEASE jar function-sample Spring Cloud Function Web Support @@ -14,13 +14,12 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 1.8 - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT 1.0.27.RELEASE diff --git a/spring-cloud-function-samples/pom.xml b/spring-cloud-function-samples/pom.xml index 3750f7e8e..9185ddc07 100644 --- a/spring-cloud-function-samples/pom.xml +++ b/spring-cloud-function-samples/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-task/pom.xml b/spring-cloud-function-task/pom.xml index 933a34898..5731b90d5 100644 --- a/spring-cloud-function-task/pom.xml +++ b/spring-cloud-function-task/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-function-web/pom.xml b/spring-cloud-function-web/pom.xml index 7a694a034..d22873fde 100644 --- a/spring-cloud-function-web/pom.xml +++ b/spring-cloud-function-web/pom.xml @@ -12,10 +12,15 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT + + jakarta.servlet + jakarta.servlet-api + provided + org.springframework spring-webmvc @@ -35,11 +40,6 @@ org.springframework.cloud spring-cloud-function-context - - javax.servlet - javax.servlet-api - provided - commons-logging commons-logging diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionHandlerMapping.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionHandlerMapping.java index f22f2e2db..0423d9710 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionHandlerMapping.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/mvc/FunctionHandlerMapping.java @@ -18,7 +18,7 @@ package org.springframework.cloud.function.web.mvc; import java.util.HashMap; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +35,8 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + + /** * @author Dave Syer * @author Oleg Zhurakousky diff --git a/spring-cloud-starter-function-web/pom.xml b/spring-cloud-starter-function-web/pom.xml index ed34879fa..04fa6ce0b 100644 --- a/spring-cloud-starter-function-web/pom.xml +++ b/spring-cloud-starter-function-web/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT .. spring-cloud-starter-function-web diff --git a/spring-cloud-starter-function-webflux/pom.xml b/spring-cloud-starter-function-webflux/pom.xml index 6ee319568..994b8bf30 100644 --- a/spring-cloud-starter-function-webflux/pom.xml +++ b/spring-cloud-starter-function-webflux/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-function-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-starter-function-webflux spring-cloud-starter-function-webflux