From 33b33adb4b53dac2849421bb5e79ef41a5d57ff6 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 27 Feb 2018 10:22:44 +0000 Subject: [PATCH] Change FunctionCatalog to key off Class Makes it possible to support other "function" types in the future. The user is always taking a risk with the lookup that the object returned has the generic type desired (but that hasn't changed with this commit). FunctionCatalog is a lot simpler as a result and also a lot more flexible. --- .../aws/SpringFunctionInitializer.java | 6 +- .../azure/AzureSpringFunctionInitializer.java | 4 +- .../OpenWhiskFunctionInitializer.java | 6 +- .../function/context/FunctionCatalog.java | 14 +- .../cloud/function/context/FunctionType.java | 2 +- .../context/catalog/FunctionInspector.java | 38 +++- .../catalog/InMemoryFunctionCatalog.java | 123 +++++------ ...ntextFunctionCatalogAutoConfiguration.java | 126 ++++------- .../function/context/FunctionTypeTests.java | 6 + .../BeanFactoryFunctionCatalogTests.java | 22 +- ...FunctionCatalogAutoConfigurationTests.java | 204 ++++++++++-------- .../FunctionExtractingFunctionCatalog.java | 187 +++++++++------- ...unctionExtractingFunctionCatalogTests.java | 32 +-- .../RouteRegistryAutoConfiguration.java | 4 +- .../StreamListeningFunctionInvoker.java | 18 +- .../SupplierInvokingMessageProducer.java | 6 +- .../function/task/TaskConfiguration.java | 9 +- .../function/web/flux/FunctionController.java | 11 +- .../web/flux/FunctionHandlerMapping.java | 16 +- .../web/flux/ReactorAutoConfiguration.java | 45 +++- .../function/web/flux/StringConverter.java | 26 +++ 21 files changed, 498 insertions(+), 407 deletions(-) create mode 100644 spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/StringConverter.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java index a4af8828f..323470ebf 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/SpringFunctionInitializer.java @@ -99,17 +99,17 @@ public class SpringFunctionInitializer implements Closeable { this.function = context.getBean(name, Function.class); } else { - this.function = this.catalog.lookupFunction(name); + this.function = this.catalog.lookup(Function.class, name); if (this.function == null) { if (defaultName) { name = "consumer"; } - this.consumer = this.catalog.lookupConsumer(name); + this.consumer = this.catalog.lookup(Consumer.class, name); if (this.consumer == null) { if (defaultName) { name = "supplier"; } - this.supplier = this.catalog.lookupSupplier(name); + this.supplier = this.catalog.lookup(Supplier.class, name); } } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringFunctionInitializer.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringFunctionInitializer.java index 023700301..cc165a886 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringFunctionInitializer.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringFunctionInitializer.java @@ -105,8 +105,8 @@ public class AzureSpringFunctionInitializer implements Closeable { this.function = context.getBean(name, Function.class); } else { - Set functionNames = this.catalog.getFunctionNames(); - this.function = this.catalog.lookupFunction(functionNames.iterator().next()); + Set functionNames = this.catalog.getNames(Function.class); + this.function = this.catalog.lookup(Function.class, functionNames.iterator().next()); } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/src/main/java/org/springframework/cloud/function/adapter/openwhisk/OpenWhiskFunctionInitializer.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/src/main/java/org/springframework/cloud/function/adapter/openwhisk/OpenWhiskFunctionInitializer.java index dbcfda264..5866d7171 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/src/main/java/org/springframework/cloud/function/adapter/openwhisk/OpenWhiskFunctionInitializer.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-openwhisk/src/main/java/org/springframework/cloud/function/adapter/openwhisk/OpenWhiskFunctionInitializer.java @@ -64,11 +64,11 @@ public class OpenWhiskFunctionInitializer { String name = this.properties.getName(); String type = this.properties.getType(); if ("function".equals(type)) { - this.function = this.catalog.lookupFunction(name); + this.function = this.catalog.lookup(Function.class, name); } else if ("consumer".equals(type)) { - this.consumer = this.catalog.lookupConsumer(name); + this.consumer = this.catalog.lookup(Consumer.class, name); } else if ("supplier".equals(type)) { - this.supplier = this.catalog.lookupSupplier(name); + this.supplier = this.catalog.lookup(Supplier.class, name); } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionCatalog.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionCatalog.java index 3a9391414..9ef56c7b5 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionCatalog.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionCatalog.java @@ -17,24 +17,14 @@ package org.springframework.cloud.function.context; import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; /** * @author Dave Syer */ public interface FunctionCatalog { - Supplier lookupSupplier(String name); + T lookup(Class type, String name); - Function lookupFunction(String name); + Set getNames(Class type); - Consumer lookupConsumer(String name); - - Set getSupplierNames(); - - Set getFunctionNames(); - - Set getConsumerNames(); } 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 index 208063216..e3aed9c44 100644 --- 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 @@ -118,7 +118,7 @@ public class FunctionType { } public FunctionType wrap(Class wrapper) { - if (wrapper.isAssignableFrom(getInputWrapper())) { + if (wrapper.isAssignableFrom(getInputWrapper()) || !isWrapper(wrapper)) { return this; } return new FunctionType(ResolvableType.forClassWithGenerics(Function.class, 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 index 34f3fc298..809a7ef9d 100644 --- 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 @@ -16,24 +16,48 @@ package org.springframework.cloud.function.context.catalog; +import org.springframework.cloud.function.context.FunctionRegistration; + /** * @author Dave Syer * */ public interface FunctionInspector { - boolean isMessage(Object function); + FunctionRegistration getRegistration(Object function); - Class getInputType(Object function); + default boolean isMessage(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? false : registration.getType().isMessage(); + } - Class getOutputType(Object function); + default Class getInputType(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? Object.class + : registration.getType().getInputType(); + } - Class getInputWrapper(Object function); + default Class getOutputType(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? Object.class + : registration.getType().getOutputType(); + } - Class getOutputWrapper(Object function); + default Class getInputWrapper(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? Object.class + : registration.getType().getInputWrapper(); + } - Object convert(Object function, String value); + default Class getOutputWrapper(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? Object.class + : registration.getType().getOutputWrapper(); + } - String getName(Object function); + default String getName(Object function) { + FunctionRegistration registration = getRegistration(function); + return registration == null ? null : registration.getNames().iterator().next(); + } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalog.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalog.java index 528c344fb..247b57571 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalog.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalog.java @@ -42,11 +42,7 @@ import org.springframework.util.Assert; public class InMemoryFunctionCatalog implements FunctionRegistry, ApplicationEventPublisherAware { - private final Map> functions; - - private final Map> consumers; - - private final Map> suppliers; + private final Map, Map> functions; @Autowired(required = false) private ApplicationEventPublisher publisher; @@ -57,49 +53,42 @@ public class InMemoryFunctionCatalog public InMemoryFunctionCatalog(Set> registrations) { Assert.notNull(registrations, "'registrations' must not be null"); - this.suppliers = new HashMap<>(); this.functions = new HashMap<>(); - this.consumers = new HashMap<>(); - registrations.stream().forEach(reg -> reg.getNames().stream().forEach(name -> { - if (reg.getTarget() instanceof Consumer) { - consumers.put(name, (Consumer) reg.getTarget()); - } - else if (reg.getTarget() instanceof Function) { - functions.put(name, (Function) reg.getTarget()); - } - else if (reg.getTarget() instanceof Supplier) { - suppliers.put(name, (Supplier) reg.getTarget()); - } - })); + registrations.stream().forEach(reg -> register(reg)); } @Override public void register(FunctionRegistration registration) { - Map values = null; FunctionRegistrationEvent event; + Class type; if (registration.getTarget() instanceof Function) { - values = this.functions; + type = Function.class; event = new FunctionRegistrationEvent(this, Function.class, registration.getNames()); } else if (registration.getTarget() instanceof Supplier) { - values = this.suppliers; + type = Supplier.class; event = new FunctionRegistrationEvent(this, Supplier.class, registration.getNames()); } - else { - values = this.consumers; + else if (registration.getTarget() instanceof Consumer) { + type = Consumer.class; event = new FunctionRegistrationEvent(this, Consumer.class, registration.getNames()); } - @SuppressWarnings("unchecked") - Map map = (Map) values; + else { + type = Object.class; + event = new FunctionRegistrationEvent(this, Object.class, + registration.getNames()); + } + Map map = functions.computeIfAbsent(type, key -> new HashMap<>()); for (String name : registration.getNames()) { map.put(name, registration.getTarget()); } publisher.publishEvent(event); } + @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } @@ -108,16 +97,10 @@ public class InMemoryFunctionCatalog public void init() { if (publisher != null) { if (!functions.isEmpty()) { - publisher.publishEvent(new FunctionRegistrationEvent(this, Function.class, - functions.keySet())); - } - if (!consumers.isEmpty()) { - publisher.publishEvent(new FunctionRegistrationEvent(this, Consumer.class, - consumers.keySet())); - } - if (!suppliers.isEmpty()) { - publisher.publishEvent(new FunctionRegistrationEvent(this, Supplier.class, - suppliers.keySet())); + for (Class type : functions.keySet()) { + publisher.publishEvent(new FunctionRegistrationEvent(this, type, + functions.get(type).keySet())); + } } } } @@ -126,50 +109,48 @@ public class InMemoryFunctionCatalog public void close() { if (publisher != null) { if (!functions.isEmpty()) { - publisher.publishEvent(new FunctionUnregistrationEvent(this, - Function.class, functions.keySet())); - } - if (!consumers.isEmpty()) { - publisher.publishEvent(new FunctionUnregistrationEvent(this, - Consumer.class, consumers.keySet())); - } - if (!suppliers.isEmpty()) { - publisher.publishEvent(new FunctionUnregistrationEvent(this, - Supplier.class, suppliers.keySet())); + for (Class type : functions.keySet()) { + publisher.publishEvent(new FunctionUnregistrationEvent(this, type, + functions.get(type).keySet())); + } } } } @Override - @SuppressWarnings("unchecked") - public Supplier lookupSupplier(String name) { - return (Supplier) suppliers.get(name); + public T lookup(Class type, String name) { + Map map = null; + for (Class key : functions.keySet()) { + if (key != Object.class) { + if (key.isAssignableFrom(type)) { + map = functions.get(key); + break; + } + } + } + if (map == null) { + map = functions.get(Object.class); + } + @SuppressWarnings("unchecked") + T result = (T) map.get(name); + return result; } @Override - @SuppressWarnings("unchecked") - public Function lookupFunction(String name) { - return (Function) functions.get(name); + public Set getNames(Class type) { + Map map = null; + for (Class key : functions.keySet()) { + if (key != Object.class) { + if (key.isAssignableFrom(type)) { + map = functions.get(key); + break; + } + } + } + if (map == null) { + map = functions.get(Object.class); + } + return map == null ? Collections.emptySet() : map.keySet(); } - @Override - @SuppressWarnings("unchecked") - public Consumer lookupConsumer(String name) { - return (Consumer) consumers.get(name); - } - - @Override - public Set getSupplierNames() { - return suppliers.keySet(); - } - - @Override - public Set getFunctionNames() { - return functions.keySet(); - } - - @Override - public Set getConsumerNames() { - return consumers.keySet(); - } } 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 f79881fd8..972658832 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 @@ -64,8 +64,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.io.Resource; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.core.type.classreading.MethodMetadataReadingVisitor; @@ -119,38 +117,31 @@ public class ContextFunctionCatalogAutoConfiguration { @Override @SuppressWarnings("unchecked") - public Supplier lookupSupplier(String name) { - Supplier result = (Supplier) processor.lookupSupplier(name); - return result; + public T lookup(Class type, String name) { + if (Supplier.class.isAssignableFrom(type)) { + return (T) processor.lookupSupplier(name); + } + if (Consumer.class.isAssignableFrom(type)) { + return (T) processor.lookupConsumer(name); + } + if (Function.class.isAssignableFrom(type)) { + return (T) processor.lookupFunction(name); + } + return null; } @Override - @SuppressWarnings("unchecked") - public Function lookupFunction(String name) { - Function result = (Function) processor.lookupFunction(name); - return result; - } - - @Override - @SuppressWarnings("unchecked") - public Consumer lookupConsumer(String name) { - Consumer result = (Consumer) processor.lookupConsumer(name); - return result; - } - - @Override - public Set getSupplierNames() { - return this.processor.getSuppliers(); - } - - @Override - public Set getFunctionNames() { - return this.processor.getFunctions(); - } - - @Override - public Set getConsumerNames() { - return this.processor.getConsumers(); + public Set getNames(Class type) { + if (Supplier.class.isAssignableFrom(type)) { + return this.processor.getSuppliers(); + } + if (Consumer.class.isAssignableFrom(type)) { + return this.processor.getConsumers(); + } + if (Function.class.isAssignableFrom(type)) { + return this.processor.getFunctions(); + } + return Collections.emptySet(); } public BeanFactoryFunctionCatalog(ContextFunctionRegistry processor) { @@ -168,38 +159,8 @@ public class ContextFunctionCatalogAutoConfiguration { } @Override - public boolean isMessage(Object function) { - return processor.isMessage(function); - } - - @Override - public Class getInputWrapper(Object function) { - return processor.findType(function).getInputWrapper(); - } - - @Override - public Class getOutputWrapper(Object function) { - return processor.findType(function).getOutputWrapper(); - } - - @Override - public Class getInputType(Object function) { - return processor.findType(function).getInputType(); - } - - @Override - public Class getOutputType(Object function) { - return processor.findType(function).getOutputType(); - } - - @Override - public Object convert(Object function, String value) { - return processor.convert(function, value); - } - - @Override - public String getName(Object function) { - return processor.registrations.get(function); + public FunctionRegistration getRegistration(Object function) { + return processor.getRegistration(function); } } @@ -219,9 +180,7 @@ public class ContextFunctionCatalogAutoConfiguration { @Autowired private ConfigurableListableBeanFactory registry; - private ConversionService conversionService; - - private Map registrations = new HashMap<>(); + private Map names = new HashMap<>(); private Map types = new HashMap<>(); @@ -229,6 +188,14 @@ public class ContextFunctionCatalogAutoConfiguration { return this.suppliers.keySet(); } + public FunctionRegistration getRegistration(Object function) { + if (!names.containsKey(function)) { + return null; + } + return new FunctionRegistration<>(function).name(names.get(function)) + .type(findType(function).getType()); + } + public Set getConsumers() { return this.consumers.keySet(); } @@ -302,7 +269,7 @@ public class ContextFunctionCatalogAutoConfiguration { types.put(name, FunctionType.compose(input, output)); } } - registrations.put(function, name); + names.put(function, name); return function; } @@ -413,19 +380,6 @@ public class ContextFunctionCatalogAutoConfiguration { return registrations; } - private Object convert(Object function, String value) { - if (conversionService == null && registry != null) { - ConversionService conversionService = this.registry - .getConversionService(); - this.conversionService = conversionService != null ? conversionService - : new DefaultConversionService(); - } - Class type = findType(function).getInputType(); - return conversionService.canConvert(String.class, type) - ? conversionService.convert(value, type) - : value; - } - private Collection getAliases(String key) { Collection names = new LinkedHashSet<>(); String value = getQualifier(key); @@ -438,7 +392,7 @@ public class ContextFunctionCatalogAutoConfiguration { private void wrap(FunctionRegistration registration, String key) { Object target = registration.getTarget(); - this.registrations.put(target, key); + this.names.put(target, key); if (registration.getType() != null) { this.types.put(key, registration.getType()); } @@ -470,8 +424,8 @@ public class ContextFunctionCatalogAutoConfiguration { else { return; } - this.registrations.remove(target); - this.registrations.put(registration.getTarget(), key); + this.names.remove(target); + this.names.put(registration.getTarget(), key); if (publisher != null) { publisher.publishEvent(new FunctionRegistrationEvent( registration.getTarget(), type, registration.getNames())); @@ -618,12 +572,8 @@ public class ContextFunctionCatalogAutoConfiguration { return ReflectionUtils.getField(field, target); } - private boolean isMessage(Object function) { - return findType(function).isMessage(); - } - private FunctionType findType(Object function) { - String name = registrations.get(function); + String name = names.get(function); if (types.containsKey(name)) { return types.get(name); } 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 index 1691f59a7..8516e8f8e 100644 --- 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 @@ -179,6 +179,12 @@ public class FunctionTypeTests { 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 IntegerToString implements Function { @Override public String apply(Integer t) { diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/BeanFactoryFunctionCatalogTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/BeanFactoryFunctionCatalogTests.java index 675135cb6..2a5218f86 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/BeanFactoryFunctionCatalogTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/BeanFactoryFunctionCatalogTests.java @@ -45,14 +45,15 @@ public class BeanFactoryFunctionCatalogTests { @Test public void basicRegistrationFeatures() { processor.register(new FunctionRegistration<>(new Foos()).names("foos")); - Function, Flux> foos = processor.lookupFunction("foos"); + Function, Flux> foos = processor.lookup(Function.class, + "foos"); assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("4"); } @Test public void lookupFunctionWithEmptyName() { processor.register(new FunctionRegistration<>(new Foos()).names("foos")); - Function, Flux> foos = processor.lookupFunction(""); + Function, Flux> foos = processor.lookup(Function.class, ""); assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("4"); } @@ -61,7 +62,7 @@ public class BeanFactoryFunctionCatalogTests { processor.register(new FunctionRegistration>( (Integer i) -> "i=" + i).names("foos").type( FunctionType.from(Integer.class).to(String.class).getType())); - Function, Flux> foos = processor.lookupFunction(""); + Function, Flux> foos = processor.lookup(Function.class, ""); assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("i=2"); } @@ -72,14 +73,14 @@ public class BeanFactoryFunctionCatalogTests { ints -> ints.map(i -> "i=" + i)).names("foos") .type(FunctionType.from(Integer.class).to(String.class) .wrap(Flux.class).getType())); - Function, Flux> foos = processor.lookupFunction(""); + Function, Flux> foos = processor.lookup(Function.class, ""); assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("i=2"); } @Test public void lookupNonExistentConsumerWithEmptyName() { processor.register(new FunctionRegistration<>(new Foos()).names("foos")); - Consumer> foos = processor.lookupConsumer(""); + Consumer> foos = processor.lookup(Consumer.class, ""); assertThat(foos).isNull(); } @@ -87,8 +88,7 @@ public class BeanFactoryFunctionCatalogTests { public void composeFunction() { processor.register(new FunctionRegistration<>(new Foos()).names("foos")); processor.register(new FunctionRegistration<>(new Bars()).names("bars")); - Function, Flux> foos = processor - .lookupFunction("foos,bars"); + Function, Flux> foos = processor.lookup(Function.class, "foos,bars"); assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("Hello 4"); } @@ -96,14 +96,14 @@ public class BeanFactoryFunctionCatalogTests { public void composeSupplier() { processor.register(new FunctionRegistration<>(new Source()).names("numbers")); processor.register(new FunctionRegistration<>(new Foos()).names("foos")); - Supplier> foos = processor.lookupSupplier("numbers,foos"); + Supplier> foos = processor.lookup(Supplier.class, "numbers,foos"); assertThat(foos.get().blockFirst()).isEqualTo("6"); } @Test public void composeUniqueSupplier() { processor.register(new FunctionRegistration<>(new Source()).names("numbers")); - Supplier> foos = processor.lookupSupplier(""); + Supplier> foos = processor.lookup(Supplier.class, ""); assertThat(foos.get().blockFirst()).isEqualTo(3); } @@ -112,7 +112,7 @@ public class BeanFactoryFunctionCatalogTests { processor.register(new FunctionRegistration<>(new Foos()).names("foos")); Sink sink = new Sink(); processor.register(new FunctionRegistration<>(sink).names("sink")); - Consumer> foos = processor.lookupConsumer("foos,sink"); + Consumer> foos = processor.lookup(Consumer.class, "foos,sink"); foos.accept(Flux.just(2)); assertThat(sink.values).contains("4"); } @@ -121,7 +121,7 @@ public class BeanFactoryFunctionCatalogTests { public void composeUniqueConsumer() { Sink sink = new Sink(); processor.register(new FunctionRegistration<>(sink).names("sink")); - Consumer> foos = processor.lookupConsumer(""); + Consumer> foos = processor.lookup(Consumer.class, ""); foos.accept(Flux.just("2")); assertThat(sink.values).contains("2"); } diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfigurationTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfigurationTests.java index 747cc2fbf..74f4a870b 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfigurationTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfigurationTests.java @@ -90,30 +90,35 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void lookUps() { create(SimpleConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(context.getBean("function2")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function,function2")) + assertThat(catalog.>lookup(Function.class, "function")) .isInstanceOf(Function.class); - Function, Flux> f = catalog - .lookupFunction("function,function2,function3"); + assertThat(context.getBean("function2")).isInstanceOf(Function.class); + assertThat(catalog.>lookup(Function.class, "function,function2")) + .isInstanceOf(Function.class); + Function, Flux> f = catalog.lookup(Function.class, + "function,function2,function3"); assertThat(f).isInstanceOf(Function.class); assertThat(f.apply(Flux.just("hello")).blockFirst()) .isEqualTo("HELLOfunction2function3"); assertThat(context.getBean("supplierFoo")).isInstanceOf(Supplier.class); - assertThat(catalog.lookupSupplier("supplierFoo")).isInstanceOf(Supplier.class); + assertThat(catalog.>lookup(Supplier.class, "supplierFoo")) + .isInstanceOf(Supplier.class); assertThat(context.getBean("supplier_Foo")).isInstanceOf(Supplier.class); - assertThat(catalog.lookupSupplier("supplier_Foo")).isInstanceOf(Supplier.class); + assertThat(catalog.>lookup(Supplier.class, "supplier_Foo")) + .isInstanceOf(Supplier.class); } @Test public void ambiguousFunction() { create(AmbiguousConfiguration.class); assertThat(context.getBean("foos")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("foos")).isInstanceOf(Function.class); - assertThat(catalog.lookupConsumer("foos")).isInstanceOf(Consumer.class); - assertThat(inspector.getInputType(catalog.lookupFunction("foos"))) + assertThat(catalog.>lookup(Function.class, "foos")) + .isInstanceOf(Function.class); + assertThat(catalog.>lookup(Consumer.class, "foos")) + .isInstanceOf(Consumer.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "foos"))) .isEqualTo(String.class); - assertThat(inspector.getInputType(catalog.lookupConsumer("foos"))) + assertThat(inspector.getInputType(catalog.lookup(Consumer.class, "foos"))) .isEqualTo(Foo.class); } @@ -121,35 +126,38 @@ public class ContextFunctionCatalogAutoConfigurationTests { @Test public void composedFunction() { create(MultipleConfiguration.class); - assertThat(catalog.lookupFunction("foos,bars")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("names,foos")).isNull(); - assertThat(inspector.getInputType(catalog.lookupFunction("foos,bars"))) + assertThat(catalog.>lookup(Function.class, "foos,bars")) + .isInstanceOf(Function.class); + assertThat(catalog.>lookup(Function.class, "names,foos")).isNull(); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "foos,bars"))) .isAssignableFrom(String.class); - assertThat(inspector.getOutputType(catalog.lookupFunction("foos,bars"))) + assertThat(inspector.getOutputType(catalog.lookup(Function.class, "foos,bars"))) .isAssignableFrom(Bar.class); } @Test public void composedSupplier() { create(MultipleConfiguration.class); - assertThat(catalog.lookupSupplier("names,foos")).isInstanceOf(Supplier.class); - assertThat(catalog.lookupFunction("names,foos")).isNull(); - assertThat(inspector.getOutputType(catalog.lookupSupplier("names,foos"))) + assertThat(catalog.>lookup(Supplier.class, "names,foos")) + .isInstanceOf(Supplier.class); + assertThat(catalog.>lookup(Function.class, "names,foos")).isNull(); + assertThat(inspector.getOutputType(catalog.lookup(Supplier.class, "names,foos"))) .isAssignableFrom(Foo.class); // The input type is the same as the input type of the first element in the chain - assertThat(inspector.getInputType(catalog.lookupSupplier("names,foos"))) + assertThat(inspector.getInputType(catalog.lookup(Supplier.class, "names,foos"))) .isAssignableFrom(Void.class); } @Test public void composedConsumer() { create(MultipleConfiguration.class); - assertThat(catalog.lookupConsumer("foos,print")).isInstanceOf(Consumer.class); - assertThat(catalog.lookupFunction("foos,print")).isNull(); - assertThat(inspector.getInputType(catalog.lookupConsumer("foos,print"))) + assertThat(catalog.>lookup(Consumer.class, "foos,print")) + .isInstanceOf(Consumer.class); + assertThat(catalog.>lookup(Function.class, "foos,print")).isNull(); + assertThat(inspector.getInputType(catalog.lookup(Consumer.class, "foos,print"))) .isAssignableFrom(String.class); // The output type is the same as the output type of the last element in the chain - assertThat(inspector.getOutputType(catalog.lookupConsumer("foos,print"))) + assertThat(inspector.getOutputType(catalog.lookup(Consumer.class, "foos,print"))) .isAssignableFrom(Void.class); } @@ -157,10 +165,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void genericFunction() { create(GenericConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); } @@ -168,11 +177,13 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void fluxMessageFunction() { create(FluxMessageConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.isMessage(catalog.lookupFunction("function"))).isTrue(); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.isMessage(catalog.lookup(Function.class, "function"))) + .isTrue(); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(String.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Flux.class); } @@ -180,11 +191,13 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void publisherMessageFunction() { create(PublisherMessageConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.isMessage(catalog.lookupFunction("function"))).isTrue(); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.isMessage(catalog.lookup(Function.class, "function"))) + .isTrue(); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(String.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Publisher.class); } @@ -192,11 +205,13 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void messageFunction() { create(MessageConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.isMessage(catalog.lookupFunction("function"))).isTrue(); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.isMessage(catalog.lookup(Function.class, "function"))) + .isTrue(); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(String.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(String.class); } @@ -204,10 +219,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void genericFluxFunction() { create(GenericFluxConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Flux.class); } @@ -215,10 +231,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void externalFunction() { create(ExternalConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); } @@ -226,10 +243,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void singletonFunction() { create(SingletonConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); } @@ -237,22 +255,25 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void singletonMessageFunction() { create(SingletonMessageConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); - assertThat(inspector.isMessage(catalog.lookupFunction("function"))).isTrue(); + assertThat(inspector.isMessage(catalog.lookup(Function.class, "function"))) + .isTrue(); } @Test public void nonParametericTypeFunction() { create(NonParametricTypeSingletonConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Integer.class); } @@ -260,10 +281,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void componentScanBeanFunction() { create(ComponentScanBeanConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); } @@ -271,10 +293,11 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void componentScanFunction() { create(ComponentScanConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("function"))) + assertThat(catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("function"))) + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "function"))) .isAssignableFrom(Map.class); } @@ -283,11 +306,13 @@ public class ContextFunctionCatalogAutoConfigurationTests { try { create("greeter.jar", ComponentScanJarConfiguration.class); assertThat(context.getBean("greeter")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("greeter")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("greeter"))) - .isAssignableFrom(String.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("greeter"))) + assertThat(catalog.>lookup(Function.class, "greeter")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "greeter"))) .isAssignableFrom(String.class); + assertThat( + inspector.getInputWrapper(catalog.lookup(Function.class, "greeter"))) + .isAssignableFrom(String.class); } finally { ClassUtils.overrideThreadContextClassLoader(getClass().getClassLoader()); @@ -310,7 +335,7 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void simpleSupplier() { create(SimpleConfiguration.class); assertThat(context.getBean("supplier")).isInstanceOf(Supplier.class); - Supplier> supplier = catalog.lookupSupplier("supplier"); + Supplier> supplier = catalog.lookup(Supplier.class, "supplier"); assertThat(supplier.get().blockFirst()).isEqualTo("hello"); } @@ -318,7 +343,7 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void simpleConsumer() { create(SimpleConfiguration.class); assertThat(context.getBean("consumer")).isInstanceOf(Consumer.class); - Consumer> consumer = catalog.lookupConsumer("consumer"); + Consumer> consumer = catalog.lookup(Consumer.class, "consumer"); consumer.accept(Flux.just("foo", "bar")); assertThat(context.getBean(SimpleConfiguration.class).list).hasSize(2); } @@ -327,9 +352,10 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void qualifiedBean() { create(QualifiedConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isNull(); - assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class); - assertThat(inspector.getInputType(catalog.lookupFunction("other"))) + assertThat(catalog.>lookup(Function.class, "function")).isNull(); + assertThat(catalog.>lookup(Function.class, "other")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputType(catalog.lookup(Function.class, "other"))) .isEqualTo(String.class); } @@ -337,17 +363,21 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void aliasBean() { create(AliasConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isNotNull(); - assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class); + assertThat(catalog.>lookup(Function.class, "function")) + .isNotNull(); + assertThat(catalog.>lookup(Function.class, "other")) + .isInstanceOf(Function.class); } @Test public void registrationBean() { create(RegistrationConfiguration.class); assertThat(context.getBean("function")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("function")).isNull(); - assertThat(catalog.lookupFunction("registration")).isNull(); - assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class); + assertThat(catalog.>lookup(Function.class, "function")).isNull(); + assertThat(catalog.>lookup(Function.class, "registration")) + .isNull(); + assertThat(catalog.>lookup(Function.class, "other")) + .isInstanceOf(Function.class); } @Test @@ -357,8 +387,9 @@ public class ContextFunctionCatalogAutoConfigurationTests { "spring.cloud.function.compile.foos.inputType=String", "spring.cloud.function.compile.foos.outputType=String"); assertThat(context.getBean("foos")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("foos")).isInstanceOf(Function.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("foos"))) + assertThat(catalog.>lookup(Function.class, "foos")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "foos"))) .isEqualTo(String.class); } @@ -372,8 +403,9 @@ public class ContextFunctionCatalogAutoConfigurationTests { create(EmptyConfiguration.class, "spring.cloud.function.import.foos.location=file:./target/foos.fun"); assertThat(context.getBean("foos")).isInstanceOf(Function.class); - assertThat(catalog.lookupFunction("foos")).isInstanceOf(Function.class); - assertThat(inspector.getInputWrapper(catalog.lookupFunction("foos"))) + assertThat(catalog.>lookup(Function.class, "foos")) + .isInstanceOf(Function.class); + assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "foos"))) .isEqualTo(String.class); } @@ -384,8 +416,9 @@ public class ContextFunctionCatalogAutoConfigurationTests { + "::set", "spring.cloud.function.compile.foos.type=consumer", "spring.cloud.function.compile.foos.inputType=String"); - assertThat(catalog.lookupConsumer("foos")).isInstanceOf(Consumer.class); - assertThat(inspector.getInputWrapper(catalog.lookupConsumer("foos"))) + assertThat(catalog.>lookup(Consumer.class, "foos")) + .isInstanceOf(Consumer.class); + assertThat(inspector.getInputWrapper(catalog.lookup(Consumer.class, "foos"))) .isEqualTo(String.class); @SuppressWarnings("unchecked") Consumer consumer = (Consumer) context.getBean("foos"); @@ -399,8 +432,9 @@ public class ContextFunctionCatalogAutoConfigurationTests { "spring.cloud.function.compile.foos.lambda=f -> f.subscribe(" + getClass().getName() + "::set)", "spring.cloud.function.compile.foos.type=consumer"); - assertThat(catalog.lookupConsumer("foos")).isInstanceOf(Consumer.class); - assertThat(inspector.getInputWrapper(catalog.lookupConsumer("foos"))) + assertThat(catalog.>lookup(Consumer.class, "foos")) + .isInstanceOf(Consumer.class); + assertThat(inspector.getInputWrapper(catalog.lookup(Consumer.class, "foos"))) .isEqualTo(Flux.class); @SuppressWarnings("unchecked") Consumer> consumer = (Consumer>) context @@ -413,8 +447,10 @@ public class ContextFunctionCatalogAutoConfigurationTests { public void factoryBeanFunction() { create(FactoryBeanConfiguration.class); assertThat(this.context.getBean("function")).isInstanceOf(Function.class); - assertThat(this.catalog.lookupFunction("function")).isInstanceOf(Function.class); - Function, Flux> f = this.catalog.lookupFunction("function"); + assertThat(this.catalog.>lookup(Function.class, "function")) + .isInstanceOf(Function.class); + Function, Flux> f = this.catalog.lookup(Function.class, + "function"); assertThat(f.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO-bar"); } diff --git a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalog.java b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalog.java index 130431eab..c77b0c8dc 100644 --- a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalog.java +++ b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalog.java @@ -23,8 +23,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import org.apache.commons.logging.Log; @@ -38,6 +36,8 @@ import org.springframework.cloud.deployer.spi.core.AppDefinition; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; import org.springframework.cloud.deployer.thin.ThinJarAppDeployer; 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.FunctionInspector; import org.springframework.cloud.function.stream.config.SupplierInvokingMessageProducer; import org.springframework.cloud.stream.binder.servlet.RouteRegistrar; @@ -89,75 +89,66 @@ public class FunctionExtractingFunctionCatalog } } - @SuppressWarnings("unchecked") @Override - public Consumer lookupConsumer(String name) { - return (Consumer) lookup(name, "lookupConsumer"); + public FunctionRegistration getRegistration(Object function) { + String name = getName(function); + if (name == null) { + return null; + } + return new FunctionRegistration<>(function).name(name) + .type(findType(function).getType()); + } + + private FunctionType findType(Object function) { + FunctionType type = FunctionType.from(getInputType(function)) + .to(getOutputType(function)).wrap(getInputWrapper(function)); + if (isMessage(function)) { + type = type.message(); + } + return type; } @SuppressWarnings("unchecked") @Override - public Function lookupFunction(String name) { - return (Function) lookup(name, "lookupFunction"); + public T lookup(Class type, String name) { + return (T) lookup(type, name, "lookup"); } @SuppressWarnings("unchecked") @Override - public Supplier lookupSupplier(String name) { - return (Supplier) lookup(name, "lookupSupplier"); - } - - @SuppressWarnings("unchecked") - @Override - public Set getSupplierNames() { - return (Set) catalog("getSupplierNames"); - } - - @SuppressWarnings("unchecked") - @Override - public Set getFunctionNames() { - return (Set) catalog("getFunctionNames"); - } - - @SuppressWarnings("unchecked") - @Override - public Set getConsumerNames() { - return (Set) catalog("getConsumerNames"); + public Set getNames(Class type) { + return (Set) getNames("getNames", type); } @Override public boolean isMessage(Object function) { - return (Boolean) inspect(function, "isMessage"); + return (Boolean) type(function, "isMessage"); } @Override public Class getInputType(Object function) { - return (Class) inspect(function, "getInputType"); + return (Class) type(function, "getInputType"); } @Override public Class getOutputType(Object function) { - return (Class) inspect(function, "getOutputType"); + return (Class) type(function, "getOutputType"); } @Override public Class getInputWrapper(Object function) { - return (Class) inspect(function, "getInputWrapper"); + return (Class) type(function, "getInputWrapper"); } @Override public Class getOutputWrapper(Object function) { - return (Class) inspect(function, "getOutputWrapper"); - } - - @Override - public Object convert(Object function, String value) { - return inspect(function, "convert"); + return (Class) type(function, "getOutputWrapper"); } + @SuppressWarnings("unchecked") @Override public String getName(Object function) { - return (String) inspect(function, "getName"); + return ((Set) inspect(function, "getNames")).iterator().next(); } public String deploy(String name, String path, String... args) { @@ -208,7 +199,8 @@ public class FunctionExtractingFunctionCatalog @SuppressWarnings("unchecked") private Set getSupplierNames(String name) { String id = this.names.get(name); - return (Set) invoke(id, FunctionCatalog.class, "getSupplierNames"); + return (Set) invoke(id, FunctionCatalog.class, "getNames", + Supplier.class); } private void unregister(String name) { @@ -228,24 +220,67 @@ public class FunctionExtractingFunctionCatalog if (logger.isDebugEnabled()) { logger.debug("Inspecting " + method); } - return invoke(FunctionInspector.class, method, arg); + return invoke(FunctionInspector.class, "getRegistration", (id, result) -> { + return prefix(id, invoke(result, method)); + }, arg); } - private Object lookup(String name, String method) { + private Object type(Object arg, String method) { if (logger.isDebugEnabled()) { - logger.debug("Looking up " + name + " with " + method); + logger.debug("Inspecting " + method); } - return invoke(FunctionCatalog.class, method, name); + return invoke(invoke(invoke(FunctionInspector.class, "getRegistration", arg), + "getType"), method); } - private Object catalog(String method) { + private Object prefix(String id, Object result) { + String name = this.ids.get(id); + String prefix = name + "/"; + if (result != null) { + if (result instanceof Collection) { + Set results = new LinkedHashSet<>(); + for (Object value : (Collection) result) { + results.add(prefix + value); + } + return results; + } + else if (result instanceof String) { + if (logger.isDebugEnabled()) { + logger.debug("Prefixed (from \" + name + \"): " + result); + } + return prefix + result; + } + + else { + if (logger.isDebugEnabled()) { + logger.debug("Result (from " + name + "): " + result); + } + return result; + } + } + return null; + } + + private Object lookup(Class type, String name, String method) { + if (logger.isDebugEnabled()) { + logger.debug("Looking up " + type + " named " + name + " with " + method); + } + return invoke(FunctionCatalog.class, method, type, name); + } + + private Object getNames(String method, Class type) { if (logger.isDebugEnabled()) { logger.debug("Calling " + method); } - return invoke(FunctionCatalog.class, method); + return invoke(FunctionCatalog.class, method, type); } private Object invoke(Class type, String method, Object... arg) { + return invoke(type, method, null, arg); + } + + private Object invoke(Class type, String method, Callback callback, + Object... arg) { Set results = new LinkedHashSet<>(); Object fallback = null; for (String id : this.deployed.keySet()) { @@ -265,6 +300,9 @@ public class FunctionExtractingFunctionCatalog fallback = false; continue; } + if (callback != null) { + return callback.call(id, result); + } return result; } } @@ -274,7 +312,7 @@ public class FunctionExtractingFunctionCatalog if (logger.isDebugEnabled()) { logger.debug("Results: " + results); } - return arg.length > 0 ? null : results; + return "lookup".equals(method) ? null : results; } private Object invoke(String id, Class type, String method, Object... arg) { @@ -284,11 +322,11 @@ public class FunctionExtractingFunctionCatalog } String name = this.ids.get(id); String prefix = name + "/"; - if (arg.length == 1) { - if (arg[0] instanceof String) { - String specific = arg[0].toString(); + if (arg.length == 2 && arg[0] instanceof Class) { + if (arg[1] instanceof String) { + String specific = arg[1].toString(); if (specific.startsWith(prefix)) { - arg[0] = specific.substring(prefix.length()); + arg[1] = specific.substring(prefix.length()); } else { return null; @@ -296,39 +334,26 @@ public class FunctionExtractingFunctionCatalog } } try { - MethodInvoker invoker = new MethodInvoker(); - invoker.setTargetObject(catalog); - invoker.setTargetMethod(method); - invoker.setArguments(arg); - invoker.prepare(); - Object result = invoker.invoke(); - if (result != null) { - if (result instanceof Collection) { - Set results = new LinkedHashSet<>(); - for (Object value : (Collection) result) { - results.add(prefix + value); - } - return results; - } - else if (result instanceof String) { - if (logger.isDebugEnabled()) { - logger.debug("Prefixed (from \" + name + \"): " + result); - } - return prefix + result; - } - - else { - if (logger.isDebugEnabled()) { - logger.debug("Result (from " + name + "): " + result); - } - return result; - } - } + Object result = invoke(catalog, method, arg); + return prefix(id, result); } catch (Exception e) { throw new IllegalStateException("Cannot extract catalog", e); } - return null; + } + + private Object invoke(Object target, String method, Object... arg) { + MethodInvoker invoker = new MethodInvoker(); + invoker.setTargetObject(target); + invoker.setTargetMethod(method); + invoker.setArguments(arg); + try { + invoker.prepare(); + return invoker.invoke(); + } + catch (Exception e) { + throw new IllegalStateException("Cannot invoke method", e); + } } public Map deployed() { @@ -340,6 +365,10 @@ public class FunctionExtractingFunctionCatalog return result; } + interface Callback { + Object call(String id, Object result); + } + } class DeployedArtifact { diff --git a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalogTests.java b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalogTests.java index 930cf013f..c151098d6 100644 --- a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalogTests.java +++ b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionExtractingFunctionCatalogTests.java @@ -15,6 +15,10 @@ */ package org.springframework.cloud.function.deployer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -47,7 +51,8 @@ public class FunctionExtractingFunctionCatalogTests { @Before public void init() throws Exception { if (id == null) { - deploy("sample", "maven://io.spring.sample:function-sample:1.0.0.BUILD-SNAPSHOT"); + deploy("sample", + "maven://io.spring.sample:function-sample:1.0.0.BUILD-SNAPSHOT"); // "--debug"); id = deploy("pojos", "maven://io.spring.sample:function-sample-pojo:1.0.0.BUILD-SNAPSHOT"); @@ -64,13 +69,13 @@ public class FunctionExtractingFunctionCatalogTests { @Test public void listFunctions() throws Exception { - assertThat(deployer.getFunctionNames()).contains("sample/uppercase", + assertThat(deployer.getNames(Function.class)).contains("sample/uppercase", "pojos/uppercase"); } @Test public void nameFunction() throws Exception { - assertThat(deployer.getName(deployer.lookupFunction("sample/uppercase"))) + assertThat(deployer.getName(deployer.lookup(Function.class, "sample/uppercase"))) .isEqualTo("sample/uppercase"); } @@ -79,37 +84,40 @@ public class FunctionExtractingFunctionCatalogTests { // This one can only work if you change the boot classpath to contain reactor-core // and reactive-streams expected.expect(ClassCastException.class); - @SuppressWarnings("unchecked") - Flux result = (Flux) deployer.lookupFunction("pojos/uppercase") - .apply(Flux.just("foo")); + Function, Flux> function = deployer.lookup(Function.class, + "pojos/uppercase"); + Flux result = function.apply(Flux.just("foo")); assertThat(result.blockFirst()).isEqualTo("FOO"); } @Test public void listConsumers() throws Exception { - assertThat(deployer.getConsumerNames()).isEmpty(); + assertThat(deployer.getNames(Consumer.class)).isEmpty(); } @Test public void deployAndExtractConsumers() throws Exception { - assertThat(deployer.lookupConsumer("pojos/sink")).isNull(); + assertThat(deployer.>lookup(Consumer.class, "pojos/sink")).isNull(); } @Test public void listSuppliers() throws Exception { - assertThat(deployer.getSupplierNames()).contains("sample/words", "pojos/words"); + assertThat(deployer.getNames(Supplier.class)).contains("sample/words", + "pojos/words"); } @Test public void nameSupplier() throws Exception { - assertThat(deployer.getName(deployer.lookupSupplier("sample/words"))) + assertThat(deployer.getName(deployer.lookup(Supplier.class, "sample/words"))) .isEqualTo("sample/words"); } @Test public void deployAndExtractSuppliers() throws Exception { - assertThat(deployer.lookupSupplier("sample/words")).isNotNull(); - assertThat(deployer.lookupSupplier("pojos/words")).isNotNull(); + assertThat(deployer.>lookup(Supplier.class, "sample/words")) + .isNotNull(); + assertThat(deployer.>lookup(Supplier.class, "pojos/words")) + .isNotNull(); } private static String deploy(String name, String path, String... args) diff --git a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/RouteRegistryAutoConfiguration.java b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/RouteRegistryAutoConfiguration.java index 2e864b336..9e4568017 100644 --- a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/RouteRegistryAutoConfiguration.java +++ b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/RouteRegistryAutoConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.cloud.function.stream.config; +import java.util.function.Supplier; + import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -36,7 +38,7 @@ public class RouteRegistryAutoConfiguration { @Bean public RouteRegistry supplierRoutes(FunctionCatalog registry) { - return () -> registry.getSupplierNames(); + return () -> registry.getNames(Supplier.class); } } diff --git a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/StreamListeningFunctionInvoker.java b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/StreamListeningFunctionInvoker.java index 5000d2c1a..13d967dbb 100644 --- a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/StreamListeningFunctionInvoker.java +++ b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/StreamListeningFunctionInvoker.java @@ -88,7 +88,7 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto } private Flux> function(String name, Flux> flux) { - Function> function = functionCatalog.lookupFunction(name); + Function> function = functionCatalog.lookup(Function.class, name); return flux.publish(values -> { Flux result = function .apply(values.map(message -> convertInput(function).apply(message))); @@ -114,7 +114,7 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto } private Flux> consumer(String name, Flux> flux) { - Consumer consumer = functionCatalog.lookupConsumer(name); + Consumer consumer = functionCatalog.lookup(Consumer.class, name); consumer.accept(flux.map(message -> convertInput(consumer).apply(message)) .filter(transformed -> transformed != UNCONVERTED)); return Flux.empty(); @@ -125,7 +125,7 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto return Flux.empty(); } String name = choose(names); - if (functionCatalog.lookupConsumer(name) != null) { + if (functionCatalog.lookup(Consumer.class, name) != null) { return consumer(name, flux); } return function(name, flux); @@ -149,8 +149,8 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto name = stash(defaultRoute); } if (name == null) { - Set names = new LinkedHashSet<>(functionCatalog.getFunctionNames()); - names.addAll(functionCatalog.getConsumerNames()); + Set names = new LinkedHashSet<>(functionCatalog.getNames(Function.class)); + names.addAll(functionCatalog.getNames(Consumer.class)); List matches = new ArrayList<>(); if (names.size() == 1) { String key = names.iterator().next(); @@ -158,9 +158,9 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto } else { for (String candidate : names) { - Object function = functionCatalog.lookupFunction(candidate); + Object function = functionCatalog.lookup(Function.class, candidate); if (function == null) { - function = functionCatalog.lookupConsumer(candidate); + function = functionCatalog.lookup(Consumer.class, candidate); } if (function == null) { continue; @@ -187,13 +187,13 @@ public class StreamListeningFunctionInvoker implements SmartInitializingSingleto } private String stash(String key) { - if (functionCatalog.lookupFunction(key) != null) { + if (functionCatalog.lookup(Function.class, key) != null) { if (!processors.containsKey(key)) { processors.put(key, flux -> function(key, flux)); } return key; } - else if (functionCatalog.lookupConsumer(key) != null) { + else if (functionCatalog.lookup(Consumer.class, key) != null) { if (!processors.containsKey(key)) { processors.put(key, flux -> consumer(key, flux)); } diff --git a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/SupplierInvokingMessageProducer.java b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/SupplierInvokingMessageProducer.java index 2399ab99d..0ac92d432 100644 --- a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/SupplierInvokingMessageProducer.java +++ b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/config/SupplierInvokingMessageProducer.java @@ -51,7 +51,7 @@ public class SupplierInvokingMessageProducer extends MessageProducerSupport { @Override protected void doStart() { - for (String name : functionCatalog.getSupplierNames()) { + for (String name : functionCatalog.getNames(Supplier.class)) { start(name); } } @@ -83,7 +83,7 @@ public class SupplierInvokingMessageProducer extends MessageProducerSupport { if (!disposables.containsKey(name)) { synchronized (disposables) { if (!disposables.containsKey(name)) { - Supplier> supplier = functionCatalog.lookupSupplier(name); + Supplier> supplier = functionCatalog.lookup(Supplier.class, name); if (supplier != null) { suppliers.add(name); disposables.put(name, @@ -96,7 +96,7 @@ public class SupplierInvokingMessageProducer extends MessageProducerSupport { } private void send(String name, Object payload) { - Supplier> supplier = functionCatalog.lookupSupplier(name); + Supplier> supplier = functionCatalog.lookup(Supplier.class, name); Message message = MessageUtils.unpack(supplier, payload); message = MessageBuilder.fromMessage(message) .setHeaderIfAbsent(StreamConfigurationProperties.ROUTE_KEY, name).build(); diff --git a/spring-cloud-function-task/src/main/java/org/springframework/cloud/function/task/TaskConfiguration.java b/spring-cloud-function-task/src/main/java/org/springframework/cloud/function/task/TaskConfiguration.java index 2d2c63e97..19ef8d0a8 100644 --- a/spring-cloud-function-task/src/main/java/org/springframework/cloud/function/task/TaskConfiguration.java +++ b/spring-cloud-function-task/src/main/java/org/springframework/cloud/function/task/TaskConfiguration.java @@ -45,12 +45,9 @@ public class TaskConfiguration { @Bean public CommandLineRunner commandLineRunner(FunctionCatalog registry) { - final Supplier> supplier = registry - .lookupSupplier(properties.getSupplier()); - final Function, Flux> function = registry - .lookupFunction(properties.getFunction()); - final Consumer> consumer = registry - .lookupConsumer(properties.getConsumer()); + final Supplier> supplier = registry.lookup(Supplier.class, properties.getSupplier()); + final Function, Flux> function = registry.lookup(Function.class, properties.getFunction()); + final Consumer> consumer = registry.lookup(Consumer.class, properties.getConsumer()); CommandLineRunner runner = new CommandLineRunner() { @Override diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java index e6f67912a..f8e97c000 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionController.java @@ -30,7 +30,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; @@ -52,8 +51,11 @@ public class FunctionController { private boolean debug = false; - public FunctionController(FunctionInspector inspector) { + private StringConverter converter; + + public FunctionController(FunctionInspector inspector, StringConverter converter) { this.inspector = inspector; + this.converter = converter; } public void setDebug(boolean debug) { @@ -115,9 +117,8 @@ public class FunctionController { return debug ? result.log() : result; } - private Mono value(Function, Flux> function, - @PathVariable String value) { - Object input = inspector.convert(function, value); + private Mono value(Function, Flux> function, String value) { + Object input = converter.convert(function, value); Mono result = Mono.from(function.apply(Flux.just(input))); if (logger.isDebugEnabled()) { logger.debug("Handled GET with function"); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java index d502940ab..28c5d7a72 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/FunctionHandlerMapping.java @@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.cloud.function.context.FunctionCatalog; -import org.springframework.cloud.function.context.catalog.FunctionInspector; import org.springframework.cloud.function.web.flux.constants.WebRequestConstants; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; @@ -55,11 +54,12 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping private String debug = "false"; @Autowired - public FunctionHandlerMapping(FunctionCatalog catalog, FunctionInspector inspector) { + public FunctionHandlerMapping(FunctionCatalog catalog, + FunctionController controller) { this.functions = catalog; - logger.info("FunctionCatalog: " + catalog + ", FunctionInspector: " + inspector); + logger.info("FunctionCatalog: " + catalog); setOrder(super.getOrder() - 5); - this.controller = new FunctionController(inspector); + this.controller = controller; } @Override @@ -118,12 +118,12 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping return null; } path = path.startsWith("/") ? path.substring(1) : path; - Consumer consumer = functions.lookupConsumer(path); + Consumer consumer = functions.lookup(Consumer.class, path); if (consumer != null) { request.setAttribute(WebRequestConstants.CONSUMER, consumer); return consumer; } - Function function = functions.lookupFunction(path); + Function function = functions.lookup(Function.class, path); if (function != null) { request.setAttribute(WebRequestConstants.FUNCTION, function); return function; @@ -136,7 +136,7 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping return null; } path = path.startsWith("/") ? path.substring(1) : path; - Supplier supplier = functions.lookupSupplier(path); + Supplier supplier = functions.lookup(Supplier.class, path); if (supplier != null) { request.setAttribute(WebRequestConstants.SUPPLIER, supplier); return supplier; @@ -152,7 +152,7 @@ public class FunctionHandlerMapping extends RequestMappingHandlerMapping name = builder.toString(); value = path.length() > name.length() ? path.substring(name.length() + 1) : null; - Function function = functions.lookupFunction(name); + Function function = functions.lookup(Function.class, name); if (function != null) { request.setAttribute(WebRequestConstants.FUNCTION, function); request.setAttribute(WebRequestConstants.ARGUMENT, value); diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/ReactorAutoConfiguration.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/ReactorAutoConfiguration.java index 7f718f2fe..b39872094 100644 --- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/ReactorAutoConfiguration.java +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/ReactorAutoConfiguration.java @@ -23,8 +23,10 @@ import com.google.gson.Gson; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; @@ -36,6 +38,9 @@ import org.springframework.cloud.function.web.flux.response.FluxReturnValueHandl import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.util.ClassUtils; @@ -54,6 +59,7 @@ import reactor.core.publisher.Flux; @ConditionalOnWebApplication @ConditionalOnClass({ Flux.class, AsyncHandlerMethodReturnValueHandler.class }) @AutoConfigureBefore(HttpMessageConvertersAutoConfiguration.class) +@Import(FunctionController.class) public class ReactorAutoConfiguration { @Autowired @@ -61,8 +67,15 @@ public class ReactorAutoConfiguration { @Bean public FunctionHandlerMapping functionHandlerMapping(FunctionCatalog catalog, - FunctionInspector inspector) { - return new FunctionHandlerMapping(catalog, inspector); + FunctionController controller) { + return new FunctionHandlerMapping(catalog, controller); + } + + @Bean + @ConditionalOnMissingBean + public StringConverter functionStringConverter(FunctionInspector inspector, + ConfigurableListableBeanFactory beanFactory) { + return new BasicStringConverter(inspector, beanFactory); } // TODO: remove this when https://jira.spring.io/browse/SPR-16529 is resolved @@ -125,4 +138,32 @@ public class ReactorAutoConfiguration { }; } + + private static class BasicStringConverter implements StringConverter { + + private ConversionService conversionService; + private ConfigurableListableBeanFactory registry; + private FunctionInspector inspector; + + public BasicStringConverter(FunctionInspector inspector, + ConfigurableListableBeanFactory registry) { + this.inspector = inspector; + this.registry = registry; + } + + @Override + public Object convert(Object function, String value) { + if (conversionService == null && registry != null) { + ConversionService conversionService = this.registry + .getConversionService(); + this.conversionService = conversionService != null ? conversionService + : new DefaultConversionService(); + } + Class type = inspector.getInputType(function); + return conversionService.canConvert(String.class, type) + ? conversionService.convert(value, type) + : value; + } + + } } diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/StringConverter.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/StringConverter.java new file mode 100644 index 000000000..c3ad9d96c --- /dev/null +++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/flux/StringConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016-2017 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 + * + * http://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.flux; + +/** + * @author Dave Syer + * + */ +public interface StringConverter { + + Object convert(Object function, String value); + +}