add support for simple (non-Flux) types

add objectToStringHttpMessageConverter

CompilerController accepts parameterized types
This commit is contained in:
markfisher
2017-02-20 10:31:22 -05:00
parent 22cab6a3f1
commit 19fd056a5e
30 changed files with 717 additions and 83 deletions

View File

@@ -41,6 +41,11 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* 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.
@@ -26,6 +26,8 @@ import org.springframework.cloud.function.compiler.java.CompilationFailedExcepti
import org.springframework.cloud.function.compiler.java.CompilationMessage;
import org.springframework.cloud.function.compiler.java.CompilationResult;
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author Andy Clement
@@ -59,13 +61,13 @@ public abstract class AbstractFunctionCompiler<F> {
private final ResultType resultType;
private final String parameterizedTypes;
private final String[] defaultResultTypeParameterizations;
private final RuntimeJavaCompiler compiler = new RuntimeJavaCompiler();
AbstractFunctionCompiler(ResultType type, String parameterizedTypes) {
AbstractFunctionCompiler(ResultType type, String... defaultResultTypeParameterizations) {
this.resultType = type;
this.parameterizedTypes = parameterizedTypes;
this.defaultResultTypeParameterizations = defaultResultTypeParameterizations;
}
/**
@@ -80,23 +82,25 @@ public abstract class AbstractFunctionCompiler<F> {
*
* @return a factory instance
*/
public CompiledFunctionFactory<F> compile(String name, String code) {
public CompiledFunctionFactory<F> compile(String name, String code, String... resultTypeParameterizations) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("name must not be empty");
}
logger.info("Initial code property value :'{}'", code);
String parameterizations = StringUtils.arrayToCommaDelimitedString(
(!ObjectUtils.isEmpty(resultTypeParameterizations)) ? resultTypeParameterizations : this.defaultResultTypeParameterizations);
code = decode(code);
if (code.startsWith("\"") && code.endsWith("\"")) {
code = code.substring(1,code.length()-1);
}
if (!code.startsWith("return ") && !code.endsWith(";")) {
code = String.format("return (%s<%s> & java.io.Serializable) %s;", resultType, this.parameterizedTypes, code);
code = String.format("return (%s<%s> & java.io.Serializable) %s;", resultType, parameterizations, code);
}
logger.info("Processed code property value :\n{}\n", code);
String firstLetter = name.substring(0, 1).toUpperCase();
name = (name.length() > 1) ? firstLetter + name.substring(1) : firstLetter;
String className = String.format("%s.%s%sFactory", this.getClass().getPackage().getName(), name, resultType);
CompilationResult compilationResult = buildAndCompileSourceCode(className, code);
CompilationResult compilationResult = buildAndCompileSourceCode(className, code, parameterizations);
if (compilationResult.wasSuccessful()) {
return new CompiledFunctionFactory(className, compilationResult);
}
@@ -107,17 +111,18 @@ public abstract class AbstractFunctionCompiler<F> {
/**
* Create the source for and then compile and load a class that embodies
* the supplied methodBody. The methodBody is inserted into a class template that
* returns a <tt>Function&lt;Flux&lt;Object&gt;,Flux&lt;Object&gt;&gt;</tt>.
* returns the specified parameterized type.
* This method can return more than one class if the method body includes local class
* declarations. An example methodBody would be <tt>return input -> input.buffer(5).map(list->list.get(0));</tt>.
*
* @param className the name of the class
* @param methodBody the source code for a method that should return a
* <tt>Function&lt;Flux&lt;Object&gt;,Flux&lt;Object&gt;&gt;</tt>
* @param methodBody the source code for a method
* @param parameterTypeString the String representation for the parameterized return type, e.g.:
* <tt>&lt;Flux&lt;Object&gt;,Flux&lt;Object&gt;</tt>
* @return the list of Classes produced by compiling and then loading the snippet of code
*/
private CompilationResult buildAndCompileSourceCode(String className, String methodBody) {
String sourceCode = makeSourceClassDefinition(className, methodBody);
private CompilationResult buildAndCompileSourceCode(String className, String methodBody, String parameterTypeString) {
String sourceCode = makeSourceClassDefinition(className, methodBody, parameterTypeString);
return compiler.compile(className, sourceCode);
}
@@ -133,9 +138,9 @@ public abstract class AbstractFunctionCompiler<F> {
* @param methodBody the code to insert into the Reactive source class template
* @return a complete Java Class definition
*/
private String makeSourceClassDefinition(String className, String methodBody) {
private String makeSourceClassDefinition(String className, String methodBody, String types) {
String shortClassName = className.substring(className.lastIndexOf('.') + 1);
String s = String.format(SOURCE_CODE_TEMPLATE, shortClassName, resultType, resultType, this.parameterizedTypes, methodBody);
String s = String.format(SOURCE_CODE_TEMPLATE, shortClassName, resultType, resultType, types, methodBody);
System.out.println(s);
return s;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* 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.
@@ -24,6 +24,14 @@ import java.util.function.Function;
public class FunctionCompiler<T, R> extends AbstractFunctionCompiler<Function<T, R>> {
public FunctionCompiler() {
super(ResultType.Function, "Flux<Object>, Flux<Object>");
this("Flux<Object>");
}
public FunctionCompiler(String type) {
this(type, type);
}
public FunctionCompiler(String inputType, String outputType) {
super(ResultType.Function, String.format("%s, %s", inputType, outputType));
}
}

View File

@@ -72,8 +72,8 @@ public class CompiledFunctionRegistry {
this.consumerDirectory.mkdir();
}
public void registerSupplier(String name, String supplier) {
CompiledFunctionFactory<?> factory = this.supplierCompiler.compile(name, supplier);
public void registerSupplier(String name, String supplier, String type) {
CompiledFunctionFactory<?> factory = this.supplierCompiler.compile(name, supplier, type);
File file = new File(this.supplierDirectory, fileName(name));
try {
FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);
@@ -83,8 +83,8 @@ public class CompiledFunctionRegistry {
}
}
public void registerFunction(String name, String function) {
CompiledFunctionFactory<?> factory = this.functionCompiler.compile(name, function);
public void registerFunction(String name, String function, String... types) {
CompiledFunctionFactory<?> factory = this.functionCompiler.compile(name, function, types);
File file = new File(this.functionDirectory, fileName(name));
try {
FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.function.compiler.app;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -29,20 +30,21 @@ public class CompilerController {
private final CompiledFunctionRegistry registry = new CompiledFunctionRegistry();
@PostMapping(path = "/{type}/{name}")
public void registerFunction(@PathVariable String type, @PathVariable String name, @RequestBody String lambda) {
switch (type) {
case "supplier":
registry.registerSupplier(name, lambda);
break;
case "function":
registry.registerFunction(name, lambda);
break;
case "consumer":
registry.registerConsumer(name, lambda);
break;
default:
throw new IllegalArgumentException("unknown type: " + type);
}
@PostMapping(path = "/supplier/{name}")
public void registerSupplier(@PathVariable String name, @RequestBody String lambda,
@RequestParam(defaultValue="Flux<String>") String type) {
this.registry.registerSupplier(name, lambda, type);
}
@PostMapping(path = "/function/{name}")
public void registerFunction(@PathVariable String name, @RequestBody String lambda,
@RequestParam(defaultValue="Flux<String>") String inputType,
@RequestParam(defaultValue="Flux<String>") String outputType) {
this.registry.registerFunction(name, lambda, inputType, outputType);
}
@PostMapping(path = "/consumer/{name}")
public void registerConsumer(@PathVariable String name, @RequestBody String lambda) {
this.registry.registerConsumer(name, lambda);
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.function.compiler.config;
import java.util.Map;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -68,7 +69,9 @@ public class FunctionProxyApplicationListener implements ApplicationListener<App
registerByteCodeLoadingProxy(name, type, context.getResource(bytecodeResource), beanFactory);
}
else {
registerLambdaCompilingProxy(name, type, lambda, beanFactory);
String inputType = properties.get("inputType");
String outputType = properties.get("outputType");
registerLambdaCompilingProxy(name, type, inputType, outputType, lambda, beanFactory);
}
}
}
@@ -91,14 +94,18 @@ public class FunctionProxyApplicationListener implements ApplicationListener<App
beanFactory.registerBeanDefinition(name, beanDefinition);
}
private void registerLambdaCompilingProxy(String name, String type, String lambda, DefaultListableBeanFactory beanFactory) {
private void registerLambdaCompilingProxy(String name, String type, String inputType, String outputType, String lambda, DefaultListableBeanFactory beanFactory) {
Resource resource = new ByteArrayResource(lambda.getBytes());
ConstructorArgumentValues args = new ConstructorArgumentValues();
MutablePropertyValues props = new MutablePropertyValues();
args.addGenericArgumentValue(resource);
Class<?> proxyClass = null;
if ("supplier".equals(type.toLowerCase())) {
proxyClass = LambdaCompilingSupplier.class;
args.addGenericArgumentValue(this.supplierCompiler);
if (outputType != null) {
props.add("typeParameterizations", outputType);
}
}
else if ("consumer".equals(type.toLowerCase())) {
proxyClass = LambdaCompilingConsumer.class;
@@ -107,9 +114,16 @@ public class FunctionProxyApplicationListener implements ApplicationListener<App
else {
proxyClass = LambdaCompilingFunction.class;
args.addGenericArgumentValue(this.functionCompiler);
if ((inputType == null && outputType != null) || (outputType == null && inputType != null)) {
throw new IllegalArgumentException("if either input or output type is set, the other is also required");
}
if (inputType != null) {
props.add("typeParameterizations", new String[] { inputType, outputType });
}
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(proxyClass);
beanDefinition.setConstructorArgumentValues(args);
beanDefinition.setPropertyValues(props);
beanFactory.registerBeanDefinition(name, beanDefinition);
}
}

View File

@@ -59,7 +59,7 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
}
}
protected final T getTarget() {
public final T getTarget() {
return this.target;
}
}

View File

@@ -39,6 +39,8 @@ public class AbstractLambdaCompilingProxy<T> implements InitializingBean, BeanNa
private T target;
private String[] typeParameterizations;
public AbstractLambdaCompilingProxy(Resource resource, AbstractFunctionCompiler<T> compiler) {
Assert.notNull(resource, "Resource must not be null");
Assert.notNull(compiler, "Compiler must not be null");
@@ -51,14 +53,18 @@ public class AbstractLambdaCompilingProxy<T> implements InitializingBean, BeanNa
this.beanName = beanName;
}
public void setTypeParameterizations(String... typeParameterizations) {
this.typeParameterizations = typeParameterizations;
}
@Override
public void afterPropertiesSet() throws Exception {
String lambda = FileCopyUtils.copyToString(new InputStreamReader(this.resource.getInputStream()));
CompiledFunctionFactory<T> factory = this.compiler.compile(this.beanName, lambda);
CompiledFunctionFactory<T> factory = this.compiler.compile(this.beanName, lambda, this.typeParameterizations);
this.target = factory.getResult();
}
protected final T getTarget() {
public final T getTarget() {
return this.target;
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.function.compiler.proxy;
import java.util.function.Function;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.function.support.FunctionProxy;
import org.springframework.core.io.Resource;
/**
@@ -27,7 +28,7 @@ import org.springframework.core.io.Resource;
* @param <T> Function input type
* @param <R> Function result type
*/
public class ByteCodeLoadingFunction<T, R> extends AbstractByteCodeLoadingProxy<Function<T, R>> implements Function<T, R>, InitializingBean {
public class ByteCodeLoadingFunction<T, R> extends AbstractByteCodeLoadingProxy<Function<T, R>> implements FunctionProxy<T, R>, InitializingBean {
public ByteCodeLoadingFunction(Resource resource) {
super(resource, Function.class);

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.function.compiler.proxy;
import java.util.function.Supplier;
import org.springframework.cloud.function.support.SupplierProxy;
import org.springframework.core.io.Resource;
/**
@@ -25,7 +26,7 @@ import org.springframework.core.io.Resource;
*
* @param <T> type
*/
public class ByteCodeLoadingSupplier<T> extends AbstractByteCodeLoadingProxy<Supplier<T>> implements Supplier<T> {
public class ByteCodeLoadingSupplier<T> extends AbstractByteCodeLoadingProxy<Supplier<T>> implements SupplierProxy<T> {
public ByteCodeLoadingSupplier(Resource resource) {
super(resource, Supplier.class);

View File

@@ -19,12 +19,13 @@ package org.springframework.cloud.function.compiler.proxy;
import java.util.function.Function;
import org.springframework.cloud.function.compiler.FunctionCompiler;
import org.springframework.cloud.function.support.FunctionProxy;
import org.springframework.core.io.Resource;
/**
* @author Mark Fisher
*/
public class LambdaCompilingFunction<T, R> extends AbstractLambdaCompilingProxy<Function<T, R>> implements Function<T, R> {
public class LambdaCompilingFunction<T, R> extends AbstractLambdaCompilingProxy<Function<T, R>> implements FunctionProxy<T, R> {
public LambdaCompilingFunction(Resource resource, FunctionCompiler<T, R> compiler) {
super(resource, compiler);

View File

@@ -19,12 +19,13 @@ package org.springframework.cloud.function.compiler.proxy;
import java.util.function.Supplier;
import org.springframework.cloud.function.compiler.SupplierCompiler;
import org.springframework.cloud.function.support.SupplierProxy;
import org.springframework.core.io.Resource;
/**
* @author Mark Fisher
*/
public class LambdaCompilingSupplier<T> extends AbstractLambdaCompilingProxy<Supplier<T>> implements Supplier<T> {
public class LambdaCompilingSupplier<T> extends AbstractLambdaCompilingProxy<Supplier<T>> implements SupplierProxy<T> {
public LambdaCompilingSupplier(Resource resource, SupplierCompiler<T> compiler) {
super(resource, compiler);