From 8d834a748366e1e248670bed8bb82d1876641096 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 28 Feb 2019 09:55:11 -0500 Subject: [PATCH] Support for implicit functions in composition If there is only one function then empty String can be used as a name to look it up. --- .../AbstractComposableFunctionRegistry.java | 66 ++++++++++++++++--- ...ntextFunctionCatalogAutoConfiguration.java | 3 +- .../catalog/InMemoryFunctionCatalogTests.java | 62 ++++++++++++++--- 3 files changed, 112 insertions(+), 19 deletions(-) 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 index f11c881ee..4ba68c889 100644 --- 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 @@ -16,6 +16,8 @@ package org.springframework.cloud.function.context.catalog; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -25,8 +27,6 @@ import java.util.concurrent.ConcurrentHashMap; 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 reactor.core.publisher.Mono; @@ -167,7 +167,8 @@ public abstract class AbstractComposableFunctionRegistry implements FunctionRegi * @return the name of the function or null. */ public String lookupFunctionName(Object function) { - return this.names.containsKey(function) ? this.names.get(function) : null; + return function != null && this.names.containsKey(function) + ? this.names.get(function) : null; } @Override @@ -311,10 +312,9 @@ public abstract class AbstractComposableFunctionRegistry implements FunctionRegi else { String[] stages = StringUtils.delimitedListToStringArray(name, "|"); - if (Stream.of(stages).allMatch(funcName -> contains(funcName))) { + List> composableFunctions = find(stages); + if (!composableFunctions.isEmpty()) { - List> composableFunctions = Stream.of(stages) - .map(funcName -> find(funcName)).collect(Collectors.toList()); FunctionRegistration composedRegistration = composableFunctions .stream().reduce((a, z) -> composeFunctions(a, z)) .orElseGet(() -> null); @@ -348,9 +348,57 @@ public abstract class AbstractComposableFunctionRegistry implements FunctionRegi return name.replaceAll(",", "|").trim(); } - private boolean contains(String name) { - return getSupplierNames().contains(name) || getFunctionNames().contains(name) - || getConsumerNames().contains(name); + private List> find(String[] stages) { + if (stages.length == 0) { + return Collections.emptyList(); + } + if (stages.length == 1) { + FunctionRegistration found = find(stages[0]); + return found == null ? Collections.emptyList() : Arrays.asList(found); + } + List> list = new ArrayList<>(); + for (int i = 0; i < stages.length; i++) { + String name = stages[i]; + FunctionRegistration registration = find(name); + if (registration != null) { + list.add(registration); + } + else { + if (i == 0) { + // Supplier or Function + if (this.suppliers.size() == 1) { + name = this.suppliers.keySet().iterator().next(); + } + else if (this.functions.size() == 1) { + name = this.functions.keySet().iterator().next(); + } + } + else if (i == stages.length - 1) { + // Consumer or Function + if (this.consumers.size() == 1) { + name = this.consumers.keySet().iterator().next(); + } + else if (this.functions.size() == 1) { + name = this.functions.keySet().iterator().next(); + } + } + else { + // Function + if (this.functions.size() == 1) { + name = this.functions.keySet().iterator().next(); + } + } + registration = find(name); + if (registration != null) { + list.add(registration); + } + else { + // Can't find default stage so we can't compose anything + return Collections.emptyList(); + } + } + } + return list; } private FunctionRegistration find(String name) { 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 a54e888c8..203a744fd 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 @@ -95,7 +95,8 @@ public class ContextFunctionCatalogAutoConfiguration { @Override public FunctionRegistration getRegistration(Object function) { - String functionName = this.lookupFunctionName(function); + String functionName = function == null ? null + : this.lookupFunctionName(function); if (StringUtils.hasText(functionName)) { FunctionRegistration registration = new FunctionRegistration( function, functionName); diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalogTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalogTests.java index 34922ffe7..17e885365 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalogTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/InMemoryFunctionCatalogTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.function.context.catalog; import java.util.function.Function; +import java.util.function.Supplier; import org.junit.Test; import reactor.core.publisher.Flux; @@ -39,7 +40,7 @@ public class InMemoryFunctionCatalogTests { public void testFunctionRegistration() { TestFunction function = new TestFunction(); FunctionRegistration registration = new FunctionRegistration<>( - function, "foo").type(FunctionType.of(TestFunction.class).getType()); + function, "foo").type(FunctionType.of(TestFunction.class)); InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); catalog.register(registration); FunctionRegistration registration2 = catalog.getRegistration(function); @@ -50,7 +51,7 @@ public class InMemoryFunctionCatalogTests { public void testFunctionLookup() { TestFunction function = new TestFunction(); FunctionRegistration registration = new FunctionRegistration<>( - function, "foo").type(FunctionType.of(TestFunction.class).getType()); + function, "foo").type(FunctionType.of(TestFunction.class)); InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); catalog.register(registration); @@ -68,10 +69,9 @@ public class InMemoryFunctionCatalogTests { @Test public void testFunctionComposition() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( - new UpperCase(), "uppercase") - .type(FunctionType.of(UpperCase.class).getType()); + new UpperCase(), "uppercase").type(FunctionType.of(UpperCase.class)); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class).getType()); + new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); catalog.register(upperCaseRegistration); catalog.register(reverseRegistration); @@ -85,14 +85,49 @@ public class InMemoryFunctionCatalogTests { .isEqualTo("RATS"); } + @Test + public void testFunctionCompositionImplicit() { + FunctionRegistration wordsRegistration = new FunctionRegistration<>( + new Words(), "words").type(FunctionType.of(Words.class)); + FunctionRegistration reverseRegistration = new FunctionRegistration<>( + new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); + catalog.register(wordsRegistration); + catalog.register(reverseRegistration); + + // There's only one function, we should be able to leave that blank + Supplier> lookedUpFunction = catalog.lookup("words|"); + assertThat(catalog.getFunctionType("words|").isMessage()).isFalse(); + + assertThat(lookedUpFunction).isNotNull(); + assertThat(lookedUpFunction.get().blockFirst()).isEqualTo("olleh"); + } + + @Test + public void testFunctionCompositionExplicit() { + FunctionRegistration wordsRegistration = new FunctionRegistration<>( + new Words(), "words").type(FunctionType.of(Words.class)); + FunctionRegistration reverseRegistration = new FunctionRegistration<>( + new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); + InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); + catalog.register(wordsRegistration); + catalog.register(reverseRegistration); + + Supplier> lookedUpFunction = catalog.lookup("words|reverse"); + assertThat(catalog.getFunctionType("words|reverse").isMessage()).isFalse(); + + assertThat(lookedUpFunction).isNotNull(); + assertThat(lookedUpFunction.get().blockFirst()).isEqualTo("olleh"); + } + @Test public void testFunctionCompositionWithMessages() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( new UpperCaseMessage(), "uppercase") - .type(FunctionType.of(UpperCaseMessage.class).getType()); + .type(FunctionType.of(UpperCaseMessage.class)); FunctionRegistration reverseRegistration = new FunctionRegistration<>( new ReverseMessage(), "reverse") - .type(FunctionType.of(ReverseMessage.class).getType()); + .type(FunctionType.of(ReverseMessage.class)); InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); catalog.register(upperCaseRegistration); catalog.register(reverseRegistration); @@ -113,9 +148,9 @@ public class InMemoryFunctionCatalogTests { public void testFunctionCompositionMixedMessages() { FunctionRegistration upperCaseRegistration = new FunctionRegistration<>( new UpperCaseMessage(), "uppercase") - .type(FunctionType.of(UpperCaseMessage.class).getType()); + .type(FunctionType.of(UpperCaseMessage.class)); FunctionRegistration reverseRegistration = new FunctionRegistration<>( - new Reverse(), "reverse").type(FunctionType.of(Reverse.class).getType()); + new Reverse(), "reverse").type(FunctionType.of(Reverse.class)); InMemoryFunctionCatalog catalog = new InMemoryFunctionCatalog(); catalog.register(upperCaseRegistration); catalog.register(reverseRegistration); @@ -132,6 +167,15 @@ public class InMemoryFunctionCatalogTests { assertThat(message.getHeaders().get("foo")).isEqualTo("bar"); } + private static class Words implements Supplier { + + @Override + public String get() { + return "hello"; + } + + } + private static class UpperCase implements Function { @Override