From ace6c16dd5f51cdd112181504c4fb205b96bdae5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 14 Nov 2022 15:01:19 +0100 Subject: [PATCH] GH-939 & GH-956 Fix Kotlin function registration regression Resolves #939 Resolves #956 polishing --- .../context/FunctionRegistration.java | 93 +--- .../AbstractComposableFunctionRegistry.java | 439 ------------------ .../context/catalog/FunctionTypeUtils.java | 13 +- .../context/FunctionRegistrationTests.java | 10 - ...onCatalogAutoConfigurationKotlinTests.java | 22 +- .../function/kotlin/ComponentUppercase.kt | 10 + .../kotlin/KotlinLambdasConfiguration.kt | 3 + 7 files changed, 59 insertions(+), 531 deletions(-) delete mode 100644 spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/AbstractComposableFunctionRegistry.java create mode 100644 spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/ComponentUppercase.kt 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..b28a9c0b2 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 @@ -29,20 +29,11 @@ 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.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.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration; +import org.springframework.core.KotlinDetector; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -123,7 +114,10 @@ public class FunctionRegistration implements BeanNameAware { } public FunctionRegistration type(FunctionType type) { - + this.type = type; + if (KotlinDetector.isKotlinPresent() && this.target instanceof KotlinLambdaToFunctionAutoConfiguration.KotlinFunctionWrapper) { + return this; + } Type t = FunctionTypeUtils.discoverFunctionTypeFromClass(this.target.getClass()); if (t == null) { // only valid for Kafka Stream KStream[] return type. return null; @@ -137,7 +131,6 @@ public class FunctionRegistration implements BeanNameAware { throw new IllegalStateException("Discovered function type does not match provided function type. Discovered: " + discoveredFunctionType + "; Provided: " + type); } - this.type = type; return this; } @@ -166,6 +159,13 @@ public class FunctionRegistration implements BeanNameAware { return this.names(Arrays.asList(names)); } + @Override + public void setBeanName(String name) { + if (CollectionUtils.isEmpty(this.names)) { + this.name(name); + } + } + /** * Transforms (wraps) function identified by the 'target' to its {@code Flux} * equivalent unless it already is. For example, {@code Function} @@ -175,66 +175,11 @@ 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()); - - 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); - } - - return result; - } - - @Override - public void setBeanName(String name) { - if (CollectionUtils.isEmpty(this.names)) { - this.name(name); - } - } - - private void isFunctionSignatureSupported() { - if (type != null) { - Assert.isTrue(!(Mono.class.isAssignableFrom(this.type.getOutputWrapper()) - && Mono.class.isAssignableFrom(this.type.getInputWrapper())), - "Function is not supported."); - } - } +// @Override +// public void setBeanName(String name) { +// if (CollectionUtils.isEmpty(this.names)) { +// this.name(name); +// } +// } } 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/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index 58581746c..ff58b449a 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 @@ -32,6 +32,8 @@ import java.util.function.Supplier; import java.util.stream.Stream; import com.fasterxml.jackson.databind.JsonNode; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; import net.jodah.typetools.TypeResolver; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -46,6 +48,7 @@ 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; +import org.springframework.core.KotlinDetector; import org.springframework.core.ResolvableType; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -171,6 +174,14 @@ public final class FunctionTypeUtils { @SuppressWarnings("unchecked") public static Type discoverFunctionTypeFromClass(Class functionalClass) { + if (KotlinDetector.isKotlinPresent()) { + if (Function1.class.isAssignableFrom(functionalClass)) { + return TypeResolver.reify(Function1.class, (Class>) functionalClass); + } + else if (Function0.class.isAssignableFrom(functionalClass)) { + return TypeResolver.reify(Function0.class, (Class>) functionalClass); + } + } if (Function.class.isAssignableFrom(functionalClass)) { for (Type superInterface : functionalClass.getGenericInterfaces()) { if (superInterface != null && !superInterface.equals(Object.class)) { @@ -187,7 +198,7 @@ public final class FunctionTypeUtils { else if (Supplier.class.isAssignableFrom(functionalClass)) { return TypeResolver.reify(Supplier.class, (Class>) functionalClass); } - return null; + return TypeResolver.reify(functionalClass); } public static Type discoverFunctionTypeFromFunctionMethod(Method functionMethod) { 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 index 10f755f55..1e2e31387 100644 --- 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 @@ -36,16 +36,6 @@ public class FunctionRegistrationTests { 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 diff --git a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java index ee37442d5..5954bf4c8 100644 --- a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java +++ b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java @@ -56,7 +56,8 @@ public class ContextFunctionCatalogAutoConfigurationKotlinTests { public void typeDiscoveryTests() { create(new Class[] { KotlinLambdasConfiguration.class, SimpleConfiguration.class, - KotlinComponentFunction.class}); + KotlinComponentFunction.class, + ComponentUppercase.class}); FunctionCatalog functionCatalog = this.context.getBean(FunctionCatalog.class); @@ -89,12 +90,19 @@ public class ContextFunctionCatalogAutoConfigurationKotlinTests { assertThat(kotlinListPojoFunction.getInputType().getTypeName()).isEqualTo("java.util.List"); assertThat(kotlinListPojoFunction.getOutputType()).isEqualTo(String.class); -// function = this.context.getBean("kotlinListPojoFunction"); -// functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinListPojoFunction", this.context); -// assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); -// assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); -// assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("java.util.List"); -// assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName()); + FunctionInvocationWrapper componentUppercase = functionCatalog.lookup("componentUppercase"); + assertThat(componentUppercase.isFunction()).isTrue(); + assertThat(componentUppercase.getInputType()).isEqualTo(String.class); + assertThat(componentUppercase.getOutputType()).isEqualTo(String.class); + + assertThat(componentUppercase.apply("hello")).isEqualTo("HELLO"); + + FunctionInvocationWrapper uppercaseBean = functionCatalog.lookup("uppercase"); + assertThat(uppercaseBean.isFunction()).isTrue(); + assertThat(uppercaseBean.getInputType()).isEqualTo(String.class); + assertThat(uppercaseBean.getOutputType()).isEqualTo(String.class); + + assertThat(uppercaseBean.apply("hello")).isEqualTo("HELLO"); } @Test diff --git a/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/ComponentUppercase.kt b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/ComponentUppercase.kt new file mode 100644 index 000000000..574b6aa48 --- /dev/null +++ b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/ComponentUppercase.kt @@ -0,0 +1,10 @@ +package org.springframework.cloud.function.kotlin + +import org.springframework.stereotype.Component + +@Component +class ComponentUppercase : (String) -> String { + override fun invoke(p1: String): String { + return p1.uppercase() + } +} diff --git a/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinLambdasConfiguration.kt b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinLambdasConfiguration.kt index 9e85d664b..ef4583d39 100644 --- a/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinLambdasConfiguration.kt +++ b/spring-cloud-function-kotlin/src/test/kotlin/org/springframework/cloud/function/kotlin/KotlinLambdasConfiguration.kt @@ -29,6 +29,9 @@ import java.util.List @EnableAutoConfiguration @Configuration class KotlinLambdasConfiguration { + + @Bean + fun uppercase(): Function = KotlinComponentFunction() @Bean fun kotlinFunction(): (String) -> String { return { it.toUpperCase() }