Add FunctionFactoryMetadata interface for type discovery
The compiled functions implement that interface which means you can inspect the signature of the method that created them and discover its generic types.
This commit is contained in:
@@ -23,7 +23,4 @@ public interface CompilationResultFactory<T> {
|
||||
|
||||
T getResult();
|
||||
|
||||
String getInputType();
|
||||
|
||||
String getOutputType();
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.function.compiler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.cloud.function.compiler.java.CompilationResult;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -33,18 +36,26 @@ public class CompiledFunctionFactory<T> implements CompilationResultFactory<T> {
|
||||
|
||||
private String outputType;
|
||||
|
||||
public CompiledFunctionFactory(String className, CompilationResult compilationResult) {
|
||||
private Method method;
|
||||
|
||||
public CompiledFunctionFactory(String className,
|
||||
CompilationResult compilationResult) {
|
||||
List<Class<?>> clazzes = compilationResult.getCompiledClasses();
|
||||
T result = null;
|
||||
for (Class<?> clazz: clazzes) {
|
||||
Method method = null;
|
||||
for (Class<?> clazz : clazzes) {
|
||||
if (clazz.getName().equals(className)) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
CompilationResultFactory<T> factory = (CompilationResultFactory<T>) clazz.newInstance();
|
||||
CompilationResultFactory<T> factory = (CompilationResultFactory<T>) clazz
|
||||
.newInstance();
|
||||
result = factory.getResult();
|
||||
method = findFactoryMethod(clazz);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException("Unexpected problem during retrieval of Function from compiled class", e);
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected problem during retrieval of Function from compiled class",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +63,29 @@ public class CompiledFunctionFactory<T> implements CompilationResultFactory<T> {
|
||||
throw new IllegalArgumentException("Failed to extract compilation result.");
|
||||
}
|
||||
this.result = result;
|
||||
this.method = method;
|
||||
this.generatedClassBytes = compilationResult.getClassBytes(className);
|
||||
}
|
||||
|
||||
private Method findFactoryMethod(Class<?> clazz) {
|
||||
AtomicReference<Method> method = new AtomicReference<>();
|
||||
ReflectionUtils.doWithLocalMethods(clazz, m -> {
|
||||
if (m.getName().equals("getResult")
|
||||
&& m.getReturnType().getName().startsWith("java.util.function")) {
|
||||
method.set(m);
|
||||
}
|
||||
});
|
||||
return method.get();
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Method getFactoryMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getInputType() {
|
||||
return inputType;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class FunctionCompiler<T, R> extends AbstractFunctionCompiler<Function<T,
|
||||
protected CompiledFunctionFactory<Function<T, R>> postProcessCompiledFunctionFactory(CompiledFunctionFactory<Function<T, R>> factory) {
|
||||
factory.setInputType(this.inputType);
|
||||
factory.setOutputType(this.outputType);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ public class CompiledFunctionRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
public void registerConsumer(String name, String consumer) {
|
||||
CompiledFunctionFactory<?> factory = this.consumerCompiler.compile(name, consumer);
|
||||
public void registerConsumer(String name, String consumer, String type) {
|
||||
CompiledFunctionFactory<?> factory = this.consumerCompiler.compile(name, consumer, type);
|
||||
File file = new File(this.consumerDirectory, fileName(name));
|
||||
try {
|
||||
FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);
|
||||
|
||||
@@ -44,7 +44,8 @@ public class CompilerController {
|
||||
}
|
||||
|
||||
@PostMapping(path = "/consumer/{name}")
|
||||
public void registerConsumer(@PathVariable String name, @RequestBody String lambda) {
|
||||
this.registry.registerConsumer(name, lambda);
|
||||
public void registerConsumer(@PathVariable String name, @RequestBody String lambda,
|
||||
@RequestParam(defaultValue="Flux<String>") String type) {
|
||||
this.registry.registerConsumer(name, lambda, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,9 @@ public class FunctionProxyApplicationListener implements ApplicationListener<App
|
||||
else if ("consumer".equals(type.toLowerCase())) {
|
||||
proxyClass = LambdaCompilingConsumer.class;
|
||||
args.addGenericArgumentValue(this.consumerCompiler);
|
||||
if (inputType != null) {
|
||||
props.add("typeParameterizations", inputType);
|
||||
}
|
||||
}
|
||||
else {
|
||||
proxyClass = LambdaCompilingFunction.class;
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.function.compiler.proxy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.function.compiler.CompilationResultFactory;
|
||||
import org.springframework.cloud.function.compiler.FunctionCompiler;
|
||||
import org.springframework.cloud.function.compiler.java.SimpleClassLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -36,6 +40,8 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
|
||||
|
||||
private final SimpleClassLoader classLoader = new SimpleClassLoader(AbstractByteCodeLoadingProxy.class.getClassLoader());
|
||||
|
||||
private Method method;
|
||||
|
||||
public AbstractByteCodeLoadingProxy(Resource resource, Class<?> type) {
|
||||
this.resource = resource;
|
||||
this.type = type;
|
||||
@@ -45,28 +51,37 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
|
||||
@SuppressWarnings("unchecked")
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(this.resource.getInputStream());
|
||||
String functionName = this.resource.getFilename().replaceAll(".fun$", "");
|
||||
String filename = this.resource.getFilename();
|
||||
String functionName = filename == null ? type.getSimpleName() : filename.replaceAll(".fun$", "");
|
||||
String firstLetter = functionName.substring(0, 1).toUpperCase();
|
||||
String upperCasedName = (functionName.length() > 1) ? firstLetter + functionName.substring(1) : firstLetter;
|
||||
String className = String.format("%s.%s%sFactory", FunctionCompiler.class.getPackage().getName(), upperCasedName, this.type.getSimpleName());
|
||||
Class<?> factoryClass = this.classLoader.defineClass(className, bytes);
|
||||
try {
|
||||
this.factory = (CompilationResultFactory<T>) factoryClass.newInstance();
|
||||
this.method = findFactoryMethod(factoryClass);
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("failed to load Function byte code", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Method findFactoryMethod(Class<?> clazz) {
|
||||
AtomicReference<Method> method = new AtomicReference<>();
|
||||
ReflectionUtils.doWithLocalMethods(clazz, m -> {
|
||||
if (m.getName().equals("getResult")
|
||||
&& m.getReturnType().getName().startsWith("java.util.function")) {
|
||||
method.set(m);
|
||||
}
|
||||
});
|
||||
return method.get();
|
||||
}
|
||||
|
||||
public final T getTarget() {
|
||||
return this.factory.getResult();
|
||||
}
|
||||
|
||||
public String getInputType() {
|
||||
return this.factory.getInputType();
|
||||
}
|
||||
|
||||
public String getOutputType() {
|
||||
return this.factory.getOutputType();
|
||||
public Method getFactoryMethod() {
|
||||
return this.method;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
package org.springframework.cloud.function.compiler.proxy;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.function.compiler.AbstractFunctionCompiler;
|
||||
import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
|
||||
import org.springframework.cloud.function.support.FunctionFactoryMetadata;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
@@ -29,7 +31,7 @@ import org.springframework.util.FileCopyUtils;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class AbstractLambdaCompilingProxy<T> implements InitializingBean, BeanNameAware {
|
||||
public class AbstractLambdaCompilingProxy<T> implements InitializingBean, BeanNameAware, FunctionFactoryMetadata {
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
@@ -67,11 +69,7 @@ public class AbstractLambdaCompilingProxy<T> implements InitializingBean, BeanNa
|
||||
return this.factory.getResult();
|
||||
}
|
||||
|
||||
public final String getInputType() {
|
||||
return this.factory.getInputType();
|
||||
}
|
||||
|
||||
public final String getOutputType() {
|
||||
return this.factory.getOutputType();
|
||||
public Method getFactoryMethod() {
|
||||
return this.factory.getFactoryMethod();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user