From b78024e7ea28278011ba182a61d241f6361d7794 Mon Sep 17 00:00:00 2001 From: markfisher Date: Sat, 1 Oct 2016 16:32:28 -0400 Subject: [PATCH] generate name per function to allow composition --- .../compiler/CompiledFunctionFactory.java | 6 +- .../function/compiler/FunctionCompiler.java | 33 +++++---- spring-cloud-function-core/pom.xml | 5 ++ .../registry/FileSystemFunctionRegistry.java | 4 +- .../registry/FunctionRegistrySupport.java | 13 ++-- .../registry/InMemoryFunctionRegistry.java | 2 +- .../FileSystemFunctionRegistryTests.java | 67 +++++++++++++++++++ 7 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 spring-cloud-function-core/src/test/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistryTests.java diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompiledFunctionFactory.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompiledFunctionFactory.java index 0063ee6ca..26938862e 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompiledFunctionFactory.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompiledFunctionFactory.java @@ -30,11 +30,11 @@ public class CompiledFunctionFactory implements FunctionFactory { private final byte[] generatedClassBytes; - public CompiledFunctionFactory(CompilationResult compilationResult) { + public CompiledFunctionFactory(String className, CompilationResult compilationResult) { List> clazzes = compilationResult.getCompiledClasses(); Function function = null; for (Class clazz: clazzes) { - if (clazz.getName().equals(FunctionCompiler.GENERATED_FUNCTION_FACTORY_CLASS_NAME)) { + if (clazz.getName().equals(className)) { try { @SuppressWarnings("unchecked") FunctionFactory functionFactory = (FunctionFactory) clazz.newInstance(); @@ -49,7 +49,7 @@ public class CompiledFunctionFactory implements FunctionFactory { throw new IllegalArgumentException("Failed to extract Function from compilation result."); } this.function = function; - this.generatedClassBytes = compilationResult.getClassBytes(FunctionCompiler.GENERATED_FUNCTION_FACTORY_CLASS_NAME); + this.generatedClassBytes = compilationResult.getClassBytes(className); } public Function getFunction() { diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/FunctionCompiler.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/FunctionCompiler.java index 5307b9eb3..19e81f148 100644 --- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/FunctionCompiler.java +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/FunctionCompiler.java @@ -33,10 +33,6 @@ import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler; */ public class FunctionCompiler { - private final static String PACKAGE = "org.springframework.cloud.function.compiler"; - - public final static String GENERATED_FUNCTION_FACTORY_CLASS_NAME = PACKAGE + ".GeneratedFunctionFactory"; - private static Logger logger = LoggerFactory.getLogger(FunctionCompiler.class); // Newlines in the property are escaped @@ -49,11 +45,11 @@ public class FunctionCompiler { * The user supplied code snippet is inserted into the template and then the result is compiled */ private static String SOURCE_CODE_TEMPLATE = - "package " + PACKAGE + ";\n" + + "package " + FunctionCompiler.class.getPackage().getName() + ";\n" + "import java.util.*;\n" + // Helpful to include this "import java.util.function.*;\n" + "import reactor.core.publisher.Flux;\n" + - "public class GeneratedFunctionFactory implements FunctionFactory {\n" + + "public class %s implements FunctionFactory {\n" + " public Function, Flux> getFunction() {\n" + " %s\n" + " }\n" + @@ -73,7 +69,10 @@ public class FunctionCompiler { * * @return a CompiledFunctionFactory instance */ - public CompiledFunctionFactory compile(String code) { + public CompiledFunctionFactory compile(String name, String code) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("name must not be empty"); + } logger.info("Initial code property value :'{}'", code); code = decode(code); if (code.startsWith("\"") && code.endsWith("\"")) { @@ -83,9 +82,12 @@ public class FunctionCompiler { code = "return (Function,Flux> & java.io.Serializable) " + code + ";"; } logger.info("Processed code property value :\n{}\n", code); - CompilationResult compilationResult = buildAndCompileSourceCode(code); + String firstLetter = name.substring(0, 1).toUpperCase(); + name = (name.length() > 1) ? firstLetter + name.substring(1) : firstLetter; + String className = String.format("%s.%sFunctionFactory", this.getClass().getPackage().getName(), name); + CompilationResult compilationResult = buildAndCompileSourceCode(className, code); if (compilationResult.wasSuccessful()) { - return new CompiledFunctionFactory<>(compilationResult); + return new CompiledFunctionFactory<>(className, compilationResult); } List compilationMessages = compilationResult.getCompilationMessages(); throw new CompilationFailedException(compilationMessages); @@ -98,13 +100,14 @@ public class FunctionCompiler { * This method can return more than one class if the method body includes local class * declarations. An example methodBody would be return input -> input.buffer(5).map(list->list.get(0));. * + * @param className the name of the class * @param methodBody the source code for a method that should return a * Function<Flux<Object>,Flux<Object>> * @return the list of Classes produced by compiling and then loading the snippet of code */ - private CompilationResult buildAndCompileSourceCode(String methodBody) { - String sourceCode = makeSourceClassDefinition(methodBody); - return compiler.compile(GENERATED_FUNCTION_FACTORY_CLASS_NAME, sourceCode); + private CompilationResult buildAndCompileSourceCode(String className, String methodBody) { + String sourceCode = makeSourceClassDefinition(className, methodBody); + return compiler.compile(className, sourceCode); } private static String decode(String input) { @@ -115,11 +118,13 @@ public class FunctionCompiler { * Make a full source code definition for a class by applying the specified method body * to the Reactive template. * + * @param className the name of the class * @param methodBody the code to insert into the Reactive source class template * @return a complete Java Class definition */ - private static String makeSourceClassDefinition(String methodBody) { - return String.format(SOURCE_CODE_TEMPLATE, methodBody); + private static String makeSourceClassDefinition(String className, String methodBody) { + String shortClassName = className.substring(className.lastIndexOf('.') + 1); + return String.format(SOURCE_CODE_TEMPLATE, shortClassName, methodBody); } } diff --git a/spring-cloud-function-core/pom.xml b/spring-cloud-function-core/pom.xml index 63e6e47dd..8b882d82e 100644 --- a/spring-cloud-function-core/pom.xml +++ b/spring-cloud-function-core/pom.xml @@ -31,6 +31,11 @@ org.springframework.boot spring-boot-starter-logging + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java index 7b5d7d28a..95dd7c3c2 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java @@ -51,7 +51,7 @@ public class FileSystemFunctionRegistry extends FunctionRegistrySupport { public Function lookup(String name) { try { byte[] bytes = FileCopyUtils.copyToByteArray(new File(this.directory, fileName(name))); - return this.deserialize(bytes); + return this.deserialize(name, bytes); } catch (IOException e) { throw new IllegalArgumentException(String.format("failed to lookup function: %s", name), e); @@ -59,7 +59,7 @@ public class FileSystemFunctionRegistry extends FunctionRegistrySupport { } public void register(String name, String function) { - CompiledFunctionFactory factory = this.compile(function); + CompiledFunctionFactory factory = this.compile(name, function); File file = new File(this.directory, fileName(name)); try { FileCopyUtils.copy(factory.getGeneratedClassBytes(), file); diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java index 076a82083..3d693a32a 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java @@ -22,6 +22,7 @@ import org.springframework.cloud.function.compiler.CompiledFunctionFactory; import org.springframework.cloud.function.compiler.FunctionCompiler; import org.springframework.cloud.function.compiler.FunctionFactory; import org.springframework.cloud.function.compiler.java.SimpleClassLoader; +import org.springframework.util.Assert; /** * @author Mark Fisher @@ -43,13 +44,17 @@ public abstract class FunctionRegistrySupport implements FunctionRegistry { return function; } - protected CompiledFunctionFactory compile(String code) { - return this.compiler.compile(code); + protected CompiledFunctionFactory compile(String name, String code) { + return this.compiler.compile(name, code); } @SuppressWarnings("unchecked") - protected Function deserialize(byte[] bytes) { - Class factoryClass = this.classLoader.defineClass(FunctionCompiler.GENERATED_FUNCTION_FACTORY_CLASS_NAME, bytes); + protected Function deserialize(String name, byte[] bytes) { + Assert.hasLength(name, "name must not be empty"); + String firstLetter = name.substring(0, 1).toUpperCase(); + name = (name.length() > 1) ? firstLetter + name.substring(1) : firstLetter; + String className = String.format("%s.%sFunctionFactory", FunctionCompiler.class.getPackage().getName(), name); + Class factoryClass = this.classLoader.defineClass(className, bytes); try { return ((FunctionFactory) factoryClass.newInstance()).getFunction(); } diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java index 4ae30168f..346725ae4 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java @@ -34,6 +34,6 @@ public class InMemoryFunctionRegistry extends FunctionRegistrySupport { @Override public void register(String name, String function) { - this.map.put(name, this.compile(function).getFunction()); + this.map.put(name, this.compile(name, function).getFunction()); } } diff --git a/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistryTests.java b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistryTests.java new file mode 100644 index 000000000..b537b2add --- /dev/null +++ b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistryTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 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.registry; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.function.Function; + +import org.junit.Before; +import org.junit.Test; + +import reactor.core.publisher.Flux; + +/** + * @author Mark Fisher + */ +public class FileSystemFunctionRegistryTests { + + private File directory; + + @Before + public void init() throws IOException { + this.directory = new File("/tmp/file-system-function-registry-tests"); + this.directory.mkdirs(); + this.directory.deleteOnExit(); + } + + @Test + public void registerAndLookup() throws IOException { + FileSystemFunctionRegistry registry = new FileSystemFunctionRegistry(this.directory); + registry.register("uppercase", "f->f.map(s->s.toString().toUpperCase())"); + Function, Flux> function = registry.lookup("uppercase"); + Flux output = function.apply(Flux.just("foo", "bar")); + List results = output.collectList().block(); + assertEquals("FOO", results.get(0)); + assertEquals("BAR", results.get(1)); + } + + @Test + public void compose() throws IOException { + FileSystemFunctionRegistry registry = new FileSystemFunctionRegistry(this.directory); + registry.register("uppercase", "f->f.map(s->s.toString().toUpperCase())"); + registry.register("exclaim", "f->f.map(s->s+\"!!!\")"); + Function, Flux> function = registry.compose("uppercase", "exclaim"); + Flux output = function.apply(Flux.just("foo", "bar")); + List results = output.collectList().block(); + assertEquals("FOO!!!", results.get(0)); + assertEquals("BAR!!!", results.get(1)); + } +}