diff --git a/README.adoc b/README.adoc
index dc40153a3..18686ccd1 100644
--- a/README.adoc
+++ b/README.adoc
@@ -156,7 +156,7 @@ curl -H "Accept: application/json" localhost:9001/words
=== Register a Consumer:
----
-./registerConsumer.sh -n print -f "System.out::println"
+./registerConsumer.sh -n print -f "f->f.subscribe(System.out::println)"
----
=== Run a REST Microservice using that Consumer:
diff --git a/scripts/registerConsumer.sh b/scripts/registerConsumer.sh
index 7836c40de..0cfbf985b 100755
--- a/scripts/registerConsumer.sh
+++ b/scripts/registerConsumer.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-while getopts ":n:f:" opt; do
+while getopts ":n:f:t:" opt; do
case $opt in
n)
NAME=$OPTARG
@@ -8,7 +8,10 @@ while getopts ":n:f:" opt; do
f)
FUNC=$OPTARG
;;
+ t)
+ TYPE=$OPTARG
+ ;;
esac
done
-curl -X POST -H "Content-Type: text/plain" -d $FUNC localhost:8080/consumer/$NAME
+curl -X POST -H "Content-Type: text/plain" -d $FUNC localhost:8080/consumer/$NAME?type=$TYPE
diff --git a/spring-cloud-function-compiler/pom.xml b/spring-cloud-function-compiler/pom.xml
index e92882002..09ac1974d 100644
--- a/spring-cloud-function-compiler/pom.xml
+++ b/spring-cloud-function-compiler/pom.xml
@@ -44,6 +44,12 @@
org.springframework.boot
spring-boot-starter-web
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompilationResultFactory.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompilationResultFactory.java
index 3f17f669a..5a4a6a46d 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompilationResultFactory.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/CompilationResultFactory.java
@@ -23,7 +23,4 @@ public interface CompilationResultFactory {
T getResult();
- String getInputType();
-
- String getOutputType();
}
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 109d15e58..c7a5797bc 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
@@ -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 implements CompilationResultFactory {
private String outputType;
- public CompiledFunctionFactory(String className, CompilationResult compilationResult) {
+ private Method method;
+
+ public CompiledFunctionFactory(String className,
+ CompilationResult compilationResult) {
List> 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 factory = (CompilationResultFactory) clazz.newInstance();
+ CompilationResultFactory factory = (CompilationResultFactory) 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 implements CompilationResultFactory {
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 = 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;
}
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 8385ef745..8431efd97 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
@@ -45,6 +45,7 @@ public class FunctionCompiler extends AbstractFunctionCompiler> postProcessCompiledFunctionFactory(CompiledFunctionFactory> factory) {
factory.setInputType(this.inputType);
factory.setOutputType(this.outputType);
+
return factory;
}
}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompiledFunctionRegistry.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompiledFunctionRegistry.java
index 7177e7675..8bee9a04e 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompiledFunctionRegistry.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompiledFunctionRegistry.java
@@ -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);
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerController.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerController.java
index 49134959a..14f6c3b02 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerController.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerController.java
@@ -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 type) {
+ this.registry.registerConsumer(name, lambda, type);
}
}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java
index ee7478d1f..b748806b5 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java
@@ -115,6 +115,9 @@ public class FunctionProxyApplicationListener implements ApplicationListener 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 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) 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 = 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;
}
}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractLambdaCompilingProxy.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractLambdaCompilingProxy.java
index af6b1b5cc..469780dd3 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractLambdaCompilingProxy.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractLambdaCompilingProxy.java
@@ -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 implements InitializingBean, BeanNameAware {
+public class AbstractLambdaCompilingProxy implements InitializingBean, BeanNameAware, FunctionFactoryMetadata {
private final Resource resource;
@@ -67,11 +69,7 @@ public class AbstractLambdaCompilingProxy 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();
}
}
diff --git a/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/ConsumerCompilerTests.java b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/ConsumerCompilerTests.java
new file mode 100644
index 000000000..d52e72d6d
--- /dev/null
+++ b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/ConsumerCompilerTests.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ * 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.compiler;
+
+import java.util.function.Consumer;
+
+import org.junit.Test;
+
+import org.springframework.cloud.function.support.FunctionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class ConsumerCompilerTests {
+
+ @Test
+ public void consumesFluxString() {
+ CompiledFunctionFactory> compiled = new ConsumerCompiler(
+ String.class.getName()).compile("foos",
+ "flux -> flux.subscribe(System.out::println)", "Flux");
+ assertThat(FunctionUtils.isFluxConsumer(compiled.getFactoryMethod())).isTrue();
+ }
+
+ @Test
+ public void consumesString() {
+ CompiledFunctionFactory> compiled = new ConsumerCompiler(
+ String.class.getName()).compile("foos", "System.out::println", "String");
+ assertThat(FunctionUtils.isFluxConsumer(compiled.getFactoryMethod())).isFalse();
+ }
+
+}
diff --git a/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/FunctionCompilerTests.java b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/FunctionCompilerTests.java
new file mode 100644
index 000000000..aa3e523cb
--- /dev/null
+++ b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/FunctionCompilerTests.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ * 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.compiler;
+
+import java.util.function.Function;
+
+import org.junit.Test;
+
+import org.springframework.cloud.function.support.FunctionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class FunctionCompilerTests {
+
+ @Test
+ public void transformsFluxString() {
+ CompiledFunctionFactory> compiled = new FunctionCompiler(
+ String.class.getName()).compile("foos",
+ "flux -> flux.map(v -> v.toUpperCase())", "Flux", "Flux");
+ assertThat(FunctionUtils.isFluxFunction(compiled.getFactoryMethod())).isTrue();
+ }
+
+ @Test
+ public void transformsString() {
+ CompiledFunctionFactory> compiled = new FunctionCompiler(
+ String.class.getName()).compile("foos", "v -> v.toUpperCase()", "String", "String");
+ assertThat(FunctionUtils.isFluxFunction(compiled.getFactoryMethod())).isFalse();
+ assertThat(compiled.getResult().apply("hello")).isEqualTo("HELLO");
+ }
+
+}
diff --git a/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/SupplierCompilerTests.java b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/SupplierCompilerTests.java
new file mode 100644
index 000000000..0fc939082
--- /dev/null
+++ b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/SupplierCompilerTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ * 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.compiler;
+
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.cloud.function.support.FunctionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class SupplierCompilerTests {
+
+ @Test
+ public void supppliesFluxString() {
+ CompiledFunctionFactory> compiled = new SupplierCompiler(
+ String.class.getName()).compile("foos",
+ "() -> Flux.just(\"foo\", \"bar\")", "Flux");
+ assertThat(FunctionUtils.isFluxSupplier(compiled.getFactoryMethod())).isTrue();
+ }
+
+ @Test
+ public void supppliesString() {
+ CompiledFunctionFactory> compiled = new SupplierCompiler(
+ String.class.getName()).compile("foos",
+ "() -> \"foo\"", "String");
+ assertThat(FunctionUtils.isFluxSupplier(compiled.getFactoryMethod())).isFalse();
+ assertThat(compiled.getResult().get()).isEqualTo("foo");
+ }
+
+}
diff --git a/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunctionTests.java b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunctionTests.java
new file mode 100644
index 000000000..de01c42da
--- /dev/null
+++ b/spring-cloud-function-compiler/test/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunctionTests.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ * 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.compiler.proxy;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
+import org.springframework.cloud.function.compiler.ConsumerCompiler;
+import org.springframework.cloud.function.compiler.FunctionCompiler;
+import org.springframework.cloud.function.compiler.SupplierCompiler;
+import org.springframework.cloud.function.support.FunctionUtils;
+import org.springframework.core.io.ByteArrayResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import reactor.core.publisher.Flux;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class ByteCodeLoadingFunctionTests {
+
+ @Test
+ public void compileConsumer() throws Exception {
+ CompiledFunctionFactory> compiled = new ConsumerCompiler(
+ String.class.getName()).compile("foos", "System.out::println", "String");
+ ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
+ @Override
+ public String getFilename() {
+ return "foos.fun";
+ }
+ };
+ ByteCodeLoadingConsumer consumer = new ByteCodeLoadingConsumer<>(resource);
+ consumer.afterPropertiesSet();
+ assertThat(FunctionUtils.isFluxConsumer(consumer.getFactoryMethod())).isFalse();
+ consumer.accept("foo");
+ }
+
+ @Test
+ public void compileSupplier() throws Exception {
+ CompiledFunctionFactory> compiled = new SupplierCompiler(
+ String.class.getName()).compile("foos", "() -> \"foo\"", "String");
+ ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
+ @Override
+ public String getFilename() {
+ return "foos.fun";
+ }
+ };
+ ByteCodeLoadingSupplier supplier = new ByteCodeLoadingSupplier<>(resource);
+ supplier.afterPropertiesSet();
+ assertThat(FunctionUtils.isFluxSupplier(supplier.getFactoryMethod())).isFalse();
+ assertThat(supplier.get()).isEqualTo("foo");
+ }
+
+ @Test
+ public void compileFunction() throws Exception {
+ CompiledFunctionFactory> compiled = new FunctionCompiler(
+ String.class.getName()).compile("foos", "v -> v.toUpperCase()", "String", "String");
+ ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
+ @Override
+ public String getFilename() {
+ return "foos.fun";
+ }
+ };
+ ByteCodeLoadingFunction function = new ByteCodeLoadingFunction<>(resource);
+ function.afterPropertiesSet();
+ assertThat(FunctionUtils.isFluxFunction(function.getFactoryMethod())).isFalse();
+ assertThat(function.apply("foo")).isEqualTo("FOO");
+ }
+
+ @Test
+ public void compileFluxFunction() throws Exception {
+ CompiledFunctionFactory, Flux>> compiled = new FunctionCompiler, Flux>(
+ String.class.getName()).compile("foos", "flux -> flux.map(v -> v.toUpperCase())", "Flux", "Flux");
+ ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
+ @Override
+ public String getFilename() {
+ return "foos.fun";
+ }
+ };
+ ByteCodeLoadingFunction, Flux> function = new ByteCodeLoadingFunction<>(resource);
+ function.afterPropertiesSet();
+ assertThat(FunctionUtils.isFluxFunction(function.getFactoryMethod())).isTrue();
+ assertThat(function.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO");
+ }
+
+}
diff --git a/spring-cloud-function-context/pom.xml b/spring-cloud-function-context/pom.xml
index 8722712d9..fce40e0f3 100644
--- a/spring-cloud-function-context/pom.xml
+++ b/spring-cloud-function-context/pom.xml
@@ -42,5 +42,11 @@
spring-boot-starter-test
test
+
+ org.springframework.cloud
+ spring-cloud-function-compiler
+ ${spring-cloud-function.version}
+ test
+
diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java
index 3ad423ef5..3caf1cc9c 100644
--- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java
+++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java
@@ -35,7 +35,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -51,6 +51,7 @@ import org.springframework.cloud.function.registry.FunctionCatalog;
import org.springframework.cloud.function.support.FluxConsumer;
import org.springframework.cloud.function.support.FluxFunction;
import org.springframework.cloud.function.support.FluxSupplier;
+import org.springframework.cloud.function.support.FunctionFactoryMetadata;
import org.springframework.cloud.function.support.FunctionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -391,7 +392,7 @@ public class ContextFunctionCatalogAutoConfiguration {
private Class> findType(String name, AbstractBeanDefinition definition,
ParamType paramType) {
Object source = definition.getSource();
- Type param;
+ Type param = null;
// Start by assuming output -> Function
int index = paramType.isOutput() ? 1 : 0;
if (source instanceof StandardMethodMetadata) {
@@ -422,18 +423,26 @@ public class ContextFunctionCatalogAutoConfiguration {
if (resolvable != null) {
param = resolvable.getGeneric(index).getGeneric(0).getType();
}
- else {
- // TODO: compiled functions (only work as String -> String)
- if (paramType.isWrapper() && !Consumer.class.isAssignableFrom(definition.getBeanClass())) {
- return Flux.class;
+ else if (registry instanceof BeanFactory) {
+ Object bean = ((BeanFactory) registry).getBean(name);
+ if (bean instanceof FunctionFactoryMetadata) {
+ FunctionFactoryMetadata factory = (FunctionFactoryMetadata) bean;
+ Type type = factory.getFactoryMethod().getGenericReturnType();
+ param = extractType(type, paramType, index);
}
- return String.class;
}
}
if (param instanceof ParameterizedType) {
ParameterizedType concrete = (ParameterizedType) param;
param = concrete.getRawType();
}
+ if (param == null) {
+ // Last ditch attempt to guess: Flux
+ if (paramType.isWrapper()) {
+ return Flux.class;
+ }
+ return String.class;
+ }
return (Class>) param;
}
diff --git a/spring-cloud-function-context/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java
similarity index 84%
rename from spring-cloud-function-context/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java
rename to spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java
index ac9ff168b..f796d6844 100644
--- a/spring-cloud-function-context/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java
+++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java
@@ -30,12 +30,16 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
+import org.springframework.cloud.function.compiler.FunctionCompiler;
import org.springframework.cloud.function.test.GenericFunction;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -143,12 +147,35 @@ public class ContextFunctionCatalogAutoConfigurationTests {
assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class);
}
- private void create(Class>... types) {
- context = new SpringApplicationBuilder((Object[]) types).run();
+ @Test
+ public void compiledFunction() throws Exception {
+ CompiledFunctionFactory> compiled = new FunctionCompiler(
+ String.class.getName()).compile("foos", "v -> v.toUpperCase()", "String",
+ "String");
+ FileSystemResource resource = new FileSystemResource("target/foos.fun");
+ StreamUtils.copy(compiled.getGeneratedClassBytes(), resource.getOutputStream());
+ create(EmptyConfiguration.class,
+ "spring.cloud.function.import.foos.location=file:./target/foos.fun");
+ assertThat(context.getBean("foos")).isInstanceOf(Function.class);
+ assertThat(catalog.lookupFunction("foos")).isInstanceOf(Function.class);
+ assertThat(inspector.getInputWrapper("foos")).isEqualTo(String.class);
+ }
+
+ private void create(Class> type, String... props) {
+ create(new Class>[] { type }, props);
+ }
+
+ private void create(Class>[] types, String... props) {
+ context = new SpringApplicationBuilder((Object[]) types).properties(props).run();
catalog = context.getBean(InMemoryFunctionCatalog.class);
inspector = context.getBean(FunctionInspector.class);
}
+ @EnableAutoConfiguration
+ @Configuration
+ protected static class EmptyConfiguration {
+ }
+
@EnableAutoConfiguration
@Configuration
protected static class SimpleConfiguration {
@@ -188,7 +215,7 @@ public class ContextFunctionCatalogAutoConfigurationTests {
@EnableAutoConfiguration
@Configuration
- @ComponentScan(basePackageClasses=GenericFunction.class)
+ @ComponentScan(basePackageClasses = GenericFunction.class)
protected static class ComponentScanConfiguration {
}
@@ -237,4 +264,3 @@ public class ContextFunctionCatalogAutoConfigurationTests {
}
}
-
diff --git a/spring-cloud-function-context/test/java/org/springframework/cloud/function/test/GenericFunction.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/test/GenericFunction.java
similarity index 100%
rename from spring-cloud-function-context/test/java/org/springframework/cloud/function/test/GenericFunction.java
rename to spring-cloud-function-context/src/test/java/org/springframework/cloud/function/test/GenericFunction.java
diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/ConsumerProxy.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/ConsumerProxy.java
index 2ffbaaa58..2c10bc5f8 100644
--- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/ConsumerProxy.java
+++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/ConsumerProxy.java
@@ -23,13 +23,12 @@ import java.util.function.Consumer;
*
* @param output type of target Consumer
*/
-public interface ConsumerProxy extends Consumer {
+public interface ConsumerProxy extends Consumer, FunctionFactoryMetadata {
default boolean isFluxConsumer() {
- return FunctionUtils.isFluxConsumer(getTarget());
+ return FunctionUtils.isFluxConsumer(getFactoryMethod());
}
Consumer getTarget();
- String getInputType();
}
diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionFactoryMetadata.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionFactoryMetadata.java
new file mode 100644
index 000000000..27b791e3b
--- /dev/null
+++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionFactoryMetadata.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ * 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.support;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author Dave Syer
+ *
+ * @param
+ */
+public interface FunctionFactoryMetadata {
+
+ Method getFactoryMethod();
+
+}
\ No newline at end of file
diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionProxy.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionProxy.java
index e84529699..cf687e067 100644
--- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionProxy.java
+++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionProxy.java
@@ -24,15 +24,12 @@ import java.util.function.Function;
* @param input type of target Function
* @param output type of target Function
*/
-public interface FunctionProxy extends Function {
+public interface FunctionProxy extends Function, FunctionFactoryMetadata {
default boolean isFluxFunction() {
- return FunctionUtils.isFluxFunction(getTarget());
+ return FunctionUtils.isFluxFunction(getFactoryMethod());
}
Function getTarget();
- String getInputType();
-
- String getOutputType();
}
diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionUtils.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionUtils.java
index 98e7e1d30..c74788fe2 100644
--- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionUtils.java
+++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/FunctionUtils.java
@@ -118,4 +118,51 @@ public abstract class FunctionUtils {
}
return typeNames.toArray(new String[typeNames.size()]);
}
+
+ public static boolean isFluxSupplier(Method method) {
+ String[] types = getParameterizedTypeNamesForMethod(method, Supplier.class);
+ if (ObjectUtils.isEmpty(types)) {
+ return false;
+ }
+ return (types[0].startsWith(FLUX_CLASS_NAME));
+ }
+
+ public static boolean isFluxConsumer(Method method) {
+ String[] types = getParameterizedTypeNamesForMethod(method, Consumer.class);
+ if (ObjectUtils.isEmpty(types)) {
+ return false;
+ }
+ return (types[0].startsWith(FLUX_CLASS_NAME));
+ }
+
+ public static boolean isFluxFunction(Method method) {
+ String[] types = getParameterizedTypeNamesForMethod(method, Function.class);
+ if (ObjectUtils.isEmpty(types)) {
+ return false;
+ }
+ if (ObjectUtils.isEmpty(types) || types.length != 2) {
+ return false;
+ }
+ return (types[0].startsWith(FLUX_CLASS_NAME)
+ && types[1].startsWith(FLUX_CLASS_NAME));
+ }
+
+ private static String[] getParameterizedTypeNamesForMethod(Method method,
+ Class> interfaceClass) {
+ Type genericInterface = method.getGenericReturnType();
+ if ((genericInterface instanceof ParameterizedType) && interfaceClass
+ .getTypeName().equals(((ParameterizedType) genericInterface)
+ .getRawType().getTypeName())) {
+ ParameterizedType type = (ParameterizedType) genericInterface;
+ Type[] args = type.getActualTypeArguments();
+ if (args != null) {
+ String[] typeNames = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ typeNames[i] = args[i].getTypeName();
+ }
+ return typeNames;
+ }
+ }
+ return new String[0];
+ }
}
diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/SupplierProxy.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/SupplierProxy.java
index 27b931c85..94a2b1029 100644
--- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/SupplierProxy.java
+++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/support/SupplierProxy.java
@@ -23,13 +23,11 @@ import java.util.function.Supplier;
*
* @param output type of target Supplier
*/
-public interface SupplierProxy extends Supplier {
+public interface SupplierProxy extends Supplier, FunctionFactoryMetadata {
default boolean isFluxSupplier() {
- return FunctionUtils.isFluxSupplier(getTarget());
+ return FunctionUtils.isFluxSupplier(getFactoryMethod());
}
Supplier getTarget();
-
- String getOutputType();
}
diff --git a/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/FunctionUtilsTests.java b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/FunctionUtilsTests.java
new file mode 100644
index 000000000..b233fbdbc
--- /dev/null
+++ b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/FunctionUtilsTests.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ * 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.gateway;
+
+import java.lang.reflect.Method;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.cloud.function.support.FunctionUtils;
+import org.springframework.util.ReflectionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import reactor.core.publisher.Flux;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class FunctionUtilsTests {
+
+ @Test
+ public void isFluxConsumer() {
+ Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxConsumer");
+ assertThat(FunctionUtils.isFluxConsumer(method)).isTrue();
+ assertThat(FunctionUtils.isFluxSupplier(method)).isFalse();
+ assertThat(FunctionUtils.isFluxFunction(method)).isFalse();
+ }
+
+ @Test
+ public void isFluxSupplier() {
+ Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxSupplier");
+ assertThat(FunctionUtils.isFluxSupplier(method)).isTrue();
+ assertThat(FunctionUtils.isFluxConsumer(method)).isFalse();
+ assertThat(FunctionUtils.isFluxFunction(method)).isFalse();
+ }
+
+ @Test
+ public void isFluxFunction() {
+ Method method = ReflectionUtils.findMethod(FunctionUtilsTests.class, "fluxFunction");
+ assertThat(FunctionUtils.isFluxFunction(method)).isTrue();
+ assertThat(FunctionUtils.isFluxSupplier(method)).isFalse();
+ assertThat(FunctionUtils.isFluxConsumer(method)).isFalse();
+ }
+
+ public Function, Flux> fluxFunction() {
+ return foos -> foos.map(foo -> new Foo());
+ }
+
+ public Supplier> fluxSupplier() {
+ return () -> Flux.just(new Foo());
+ }
+
+ public Consumer> fluxConsumer() {
+ return flux -> flux.subscribe(System.out::println);
+ }
+
+ class Foo {}
+
+}
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledConsumerTests.java b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledConsumerTests.java
index d5995ccc3..8fa4d765a 100644
--- a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledConsumerTests.java
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledConsumerTests.java
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = {
"spring.cloud.function.compile.test.lambda=com.example.SampleCompiledConsumerTests.Reference::set",
+ "spring.cloud.function.compile.test.inputType=String",
"spring.cloud.function.compile.test.type=consumer"
})
public class SampleCompiledConsumerTests {
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledFunctionTests.java b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledFunctionTests.java
index ba44a65f1..29435826c 100644
--- a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledFunctionTests.java
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleCompiledFunctionTests.java
@@ -31,8 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Mark Fisher
*/
@RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
- properties = "spring.cloud.function.compile.test.lambda=f->f.map(s->s+\"!!!\")")
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
+ "spring.cloud.function.compile.test.lambda=f->f.map(s->s+\"!!!\")",
+ "spring.cloud.function.compile.test.inputType=Flux",
+ "spring.cloud.function.compile.test.outputType=Flux" })
public class SampleCompiledFunctionTests {
@LocalServerPort
@@ -41,8 +43,8 @@ public class SampleCompiledFunctionTests {
@Test
public void lowercase() {
assertThat(new TestRestTemplate().postForObject(
- "http://localhost:" + port + "/test", "it works",
- String.class)).isEqualTo("it works!!!");
+ "http://localhost:" + port + "/test", "it works", String.class))
+ .isEqualTo("it works!!!");
}
}