add support for simple (non-Flux) types
add objectToStringHttpMessageConverter CompilerController accepts parameterized types
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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<Flux<Object>,Flux<Object>></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<Flux<Object>,Flux<Object>></tt>
|
||||
* @param methodBody the source code for a method
|
||||
* @param parameterTypeString the String representation for the parameterized return type, e.g.:
|
||||
* <tt><Flux<Object>,Flux<Object></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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
|
||||
}
|
||||
}
|
||||
|
||||
protected final T getTarget() {
|
||||
public final T getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user