diff --git a/README.adoc b/README.adoc
index 7780b0b2f..170fd494e 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,42 +1,56 @@
+To run these examples, change into the `scripts` directory:
+
+----
+cd scripts
+----
+
+== Start the Function Registry Service:
+
+----
+./function-registry.sh
+----
+
== Register a Function:
----
./registerFunction.sh -n uppercase -f "f->f.map(s->s.toString().toUpperCase())"
----
-== Run a Stream Processing Microservice using that Function:
+== Run a REST Microservice using that Function:
+
+----
+./web.sh -f uppercase -p 9000
+curl -H "Content-Type: text/plain" -H "Accept: text/plain" :9000/function -d foo
+----
+
+== Register a Supplier:
+
+----
+./registerSupplier.sh -n words -f "()->Flux.just(\"foo\",\"bar\")"
+----
+
+== Run a REST Microservice using that Supplier:
+
+----
+./web.sh -s words -p 9001
+curl -H "Accept: application/json" :9001/supplier
+----
+
+== Register a Consumer:
+
+----
+./registerConsumer.sh -n print -f "System.out::println"
+----
+
+== Run a REST Microservice using that Consumer:
+
+----
+./web.sh -c print -p 9002
+curl -X POST -H "Content-Type: text/plain" -d foo :9002/consumer
+----
+
+== Run a Stream Processing Microservice using the Function:
----
./stream.sh -i words -o uppercaseWords -f uppercase
----
-
-== Run a REST Microservice using that Function:
-
-----
-./web.sh
-curl -H "Content-Type=text/plain" localhost:8080/uppercase -d foo
-----
-
-== Compose Functions:
-
-(assuming the `uppercase` function was already registered as above)
-
-----
-./registerFunction.sh -n pluralize -f "f->f.map(s->s+\"S\")"
-
-curl -H "Content-Type=text/plain" localhost:8080/uppercase,pluralize -d foo
-----
-
-== Run a Task Microservice using a Supplier, Function, and Consumer:
-
-(assuming the `uppercase` function was already registered as above)
-
-----
-./registerSupplier.sh -n words -f "()->Flux.just(\"foo\",\"bar\")"
-
-./registerConsumer.sh -n print -f "System.out::println"
-
-./task.sh -s words -f uppercase -c print
-----
-
-(more docs soon)
diff --git a/scripts/function-registry.sh b/scripts/function-registry.sh
new file mode 100755
index 000000000..8d63c3b76
--- /dev/null
+++ b/scripts/function-registry.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+java -jar ../spring-cloud-function-compiler/target/spring-cloud-function-compiler-1.0.0.BUILD-SNAPSHOT.jar
diff --git a/scripts/registerConsumer.sh b/scripts/registerConsumer.sh
index 8b46d514a..37fa380a8 100755
--- a/scripts/registerConsumer.sh
+++ b/scripts/registerConsumer.sh
@@ -11,6 +11,4 @@ while getopts ":n:f:" opt; do
esac
done
-java -jar ../spring-cloud-function-core/target/spring-cloud-function-core-1.0.0.BUILD-SNAPSHOT-registrar.jar consumer\
- $NAME\
- $FUNC
+curl -X POST -H "Content-Type: text/plain" -d $FUNC :8080/consumer/$NAME
diff --git a/scripts/registerFunction.sh b/scripts/registerFunction.sh
index aa95fb5d3..7fcb02239 100755
--- a/scripts/registerFunction.sh
+++ b/scripts/registerFunction.sh
@@ -11,6 +11,4 @@ while getopts ":n:f:" opt; do
esac
done
-java -jar ../spring-cloud-function-core/target/spring-cloud-function-core-1.0.0.BUILD-SNAPSHOT-registrar.jar function\
- $NAME\
- $FUNC
+curl -X POST -H "Content-Type: text/plain" -d $FUNC :8080/function/$NAME
diff --git a/scripts/registerSupplier.sh b/scripts/registerSupplier.sh
index 242c47d97..84291c5b0 100755
--- a/scripts/registerSupplier.sh
+++ b/scripts/registerSupplier.sh
@@ -11,6 +11,4 @@ while getopts ":n:f:" opt; do
esac
done
-java -jar ../spring-cloud-function-core/target/spring-cloud-function-core-1.0.0.BUILD-SNAPSHOT-registrar.jar supplier\
- $NAME\
- $FUNC
+curl -X POST -H "Content-Type: text/plain" -d $FUNC :8080/supplier/$NAME
diff --git a/scripts/stream.sh b/scripts/stream.sh
index 439167799..db798f1d1 100755
--- a/scripts/stream.sh
+++ b/scripts/stream.sh
@@ -7,6 +7,7 @@ while getopts ":i:f:o:" opt; do
;;
f)
FUNC=$OPTARG
+ TYPE=function
;;
o)
OUT=$OPTARG
@@ -14,7 +15,11 @@ while getopts ":i:f:o:" opt; do
esac
done
-java -noverify -XX:TieredStopAtLevel=1 -Xss256K -Xms16M -Xmx256M -XX:MaxMetaspaceSize=128M -jar ../spring-cloud-function-stream/target/spring-cloud-function-stream-1.0.0.BUILD-SNAPSHOT.jar\
+java -jar ../spring-cloud-function-samples/spring-cloud-function-sample-bytecode/target/function-sample-bytecode-1.0.0.BUILD-SNAPSHOT.jar\
+ --management.security.enabled=false\
+ --server.port=9999\
--spring.cloud.stream.bindings.input.destination=$IN\
--spring.cloud.stream.bindings.output.destination=$OUT\
- --function.name=$FUNC
+ --function.name=$TYPE\
+ --function.type=$TYPE\
+ --function.resource=file:///tmp/function-registry/$TYPE's'/$FUNC.fun
diff --git a/scripts/web.sh b/scripts/web.sh
index 4a1ed9d14..e072d8d1f 100755
--- a/scripts/web.sh
+++ b/scripts/web.sh
@@ -1,4 +1,26 @@
#!/bin/bash
-java -jar ../spring-cloud-function-web/target/spring-cloud-function-web-1.0.0.BUILD-SNAPSHOT.jar ${@}
+while getopts ":s:f:c:p:" opt; do
+ case $opt in
+ s)
+ FUNC=$OPTARG
+ TYPE=supplier
+ ;;
+ f)
+ FUNC=$OPTARG
+ TYPE=function
+ ;;
+ c)
+ FUNC=$OPTARG
+ TYPE=consumer
+ ;;
+ p)
+ PORT=$OPTARG
+ ;;
+ esac
+done
+java -jar ../spring-cloud-function-samples/spring-cloud-function-sample-bytecode/target/function-sample-bytecode-1.0.0.BUILD-SNAPSHOT.jar \
+--function.type=$TYPE \
+--function.resource=file:///tmp/function-registry/$TYPE's'/$FUNC.fun \
+--server.port=$PORT
diff --git a/spring-cloud-function-compiler/pom.xml b/spring-cloud-function-compiler/pom.xml
index 76f96ea9c..15aa45efb 100644
--- a/spring-cloud-function-compiler/pom.xml
+++ b/spring-cloud-function-compiler/pom.xml
@@ -38,12 +38,24 @@
org.springframework.boot
- spring-boot-starter-logging
-
-
- org.springframework
- spring-context
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.springframework.boot.experimental
+ spring-boot-thin-launcher
+ ${wrapper.version}
+
+
+
+
+
+
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
new file mode 100644
index 000000000..a18782884
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompiledFunctionRegistry.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.app;
+
+import java.io.File;
+import java.io.IOException;
+
+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.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+import reactor.core.publisher.Flux;
+
+/**
+ * @author Mark Fisher
+ */
+public class CompiledFunctionRegistry {
+
+ private static final String SUPPLIER_DIRECTORY = "suppliers";
+
+ private static final String FUNCTION_DIRECTORY = "functions";
+
+ private static final String CONSUMER_DIRECTORY = "consumers";
+
+ private final File supplierDirectory;
+
+ private final File functionDirectory;
+
+ private final File consumerDirectory;
+
+ private final SupplierCompiler> supplierCompiler = new SupplierCompiler<>();
+
+ private final FunctionCompiler, Flux>> functionCompiler = new FunctionCompiler<>();
+
+ private final ConsumerCompiler> consumerCompiler = new ConsumerCompiler<>();
+
+ public CompiledFunctionRegistry() {
+ this(new File("/tmp/function-registry"));
+ }
+
+ public CompiledFunctionRegistry(File directory) {
+ Assert.notNull(directory, "Directory must not be null");
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+ else {
+ Assert.isTrue(directory.isDirectory(),
+ String.format("%s is not a directory.", directory.getAbsolutePath()));
+ }
+ this.supplierDirectory = new File(directory, SUPPLIER_DIRECTORY);
+ this.functionDirectory = new File(directory, FUNCTION_DIRECTORY);
+ this.consumerDirectory = new File(directory, CONSUMER_DIRECTORY);
+ this.supplierDirectory.mkdir();
+ this.functionDirectory.mkdir();
+ this.consumerDirectory.mkdir();
+ }
+
+ public void registerSupplier(String name, String supplier) {
+ CompiledFunctionFactory> factory = this.supplierCompiler.compile(name, supplier);
+ File file = new File(this.supplierDirectory, fileName(name));
+ try {
+ FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException(String.format("failed to register Supplier: %s", name), e);
+ }
+ }
+
+ public void registerFunction(String name, String function) {
+ CompiledFunctionFactory> factory = this.functionCompiler.compile(name, function);
+ File file = new File(this.functionDirectory, fileName(name));
+ try {
+ FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException(String.format("failed to register Function: %s", name), e);
+ }
+ }
+
+ public void registerConsumer(String name, String consumer) {
+ CompiledFunctionFactory> factory = this.consumerCompiler.compile(name, consumer);
+ File file = new File(this.consumerDirectory, fileName(name));
+ try {
+ FileCopyUtils.copy(factory.getGeneratedClassBytes(), file);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException(String.format("failed to register Consumer: %s", name), e);
+ }
+ }
+
+ private String fileName(String functionName) {
+ return String.format("%s.%s", functionName, "fun");
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerApplication.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerApplication.java
new file mode 100644
index 000000000..6142683c9
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerApplication.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.app;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CompilerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CompilerApplication.class, args);
+ }
+}
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
new file mode 100644
index 000000000..00b949f7f
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/app/CompilerController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.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.RestController;
+
+/**
+ * @author Mark Fisher
+ */
+@RestController
+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);
+ }
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractByteCodeLoadingProxy.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractByteCodeLoadingProxy.java
new file mode 100644
index 000000000..2f84d1ba8
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/AbstractByteCodeLoadingProxy.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 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 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;
+
+/**
+ * @author Mark Fisher
+ */
+public abstract class AbstractByteCodeLoadingProxy implements InitializingBean {
+
+ private final Resource resource;
+
+ private final Class> type;
+
+ private T target;
+
+ private final SimpleClassLoader classLoader = new SimpleClassLoader(AbstractByteCodeLoadingProxy.class.getClassLoader());
+
+ public AbstractByteCodeLoadingProxy(Resource resource, Class> type) {
+ this.resource = resource;
+ this.type = type;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ byte[] bytes = FileCopyUtils.copyToByteArray(this.resource.getInputStream());
+ String functionName = this.resource.getFilename().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 {
+ @SuppressWarnings("unchecked")
+ CompilationResultFactory factory = (CompilationResultFactory) factoryClass.newInstance();
+ this.target = factory.getResult();
+ }
+ catch (InstantiationException | IllegalAccessException e) {
+ throw new IllegalArgumentException("failed to load Function byte code", e);
+ }
+ }
+
+ protected final T getTarget() {
+ return this.target;
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingConsumer.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingConsumer.java
new file mode 100644
index 000000000..729d3ebd8
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingConsumer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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 org.springframework.core.io.Resource;
+
+/**
+ * @author Mark Fisher
+ *
+ * @param type
+ */
+public class ByteCodeLoadingConsumer extends AbstractByteCodeLoadingProxy> implements Consumer {
+
+ public ByteCodeLoadingConsumer(Resource resource) {
+ super(resource, Consumer.class);
+ }
+
+ @Override
+ public void accept(T t) {
+ this.getTarget().accept(t);
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunction.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunction.java
new file mode 100644
index 000000000..345daadbc
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingFunction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.Function;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+
+/**
+ * @author Mark Fisher
+ *
+ * @param Function input type
+ * @param Function result type
+ */
+public class ByteCodeLoadingFunction extends AbstractByteCodeLoadingProxy> implements Function, InitializingBean {
+
+ public ByteCodeLoadingFunction(Resource resource) {
+ super(resource, Function.class);
+ }
+
+ @Override
+ public R apply(T input) {
+ return this.getTarget().apply(input);
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingSupplier.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingSupplier.java
new file mode 100644
index 000000000..3b6fedc0b
--- /dev/null
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/ByteCodeLoadingSupplier.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.Supplier;
+
+import org.springframework.core.io.Resource;
+
+/**
+ * @author Mark Fisher
+ *
+ * @param type
+ */
+public class ByteCodeLoadingSupplier extends AbstractByteCodeLoadingProxy> implements Supplier {
+
+ public ByteCodeLoadingSupplier(Resource resource) {
+ super(resource, Supplier.class);
+ }
+
+ @Override
+ public T get() {
+ return this.getTarget().get();
+ }
+}
diff --git a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/LambdaCompilingFunction.java b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/LambdaCompilingFunction.java
similarity index 97%
rename from spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/LambdaCompilingFunction.java
rename to spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/LambdaCompilingFunction.java
index e730dfa2a..f5c930c3a 100644
--- a/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/LambdaCompilingFunction.java
+++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/proxy/LambdaCompilingFunction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.cloud.function.compiler;
+package org.springframework.cloud.function.compiler.proxy;
import java.io.InputStreamReader;
import java.util.function.Function;
diff --git a/spring-cloud-function-samples/pom.xml b/spring-cloud-function-samples/pom.xml
index f558a006c..5767b9c63 100644
--- a/spring-cloud-function-samples/pom.xml
+++ b/spring-cloud-function-samples/pom.xml
@@ -15,6 +15,8 @@
spring-cloud-function-samplespring-cloud-function-sample-pojo
+ spring-cloud-function-sample-compiler
+ spring-cloud-function-sample-bytecode
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/.jdk8 b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/.jdk8
new file mode 100644
index 000000000..e69de29bb
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/pom.xml b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/pom.xml
new file mode 100644
index 000000000..d7abadb22
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+
+ com.example
+ function-sample-bytecode
+ 1.0.0.BUILD-SNAPSHOT
+ jar
+ spring-cloud-function-sample-bytecode
+ Spring Cloud Function ByteCode Loading Support
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.0.BUILD-SNAPSHOT
+
+
+
+
+ 1.8
+ 1.0.0.BUILD-SNAPSHOT
+ 0.0.1.BUILD-SNAPSHOT
+ 3.0.4.RELEASE
+
+
+
+
+
+ io.projectreactor
+ reactor-core
+ ${reactor.version}
+
+
+ org.springframework.cloud
+ spring-cloud-function-web
+ ${spring-cloud-function.version}
+
+
+ org.springframework.cloud
+ spring-cloud-function-stream
+ ${spring-cloud-function.version}
+
+
+ org.springframework.cloud
+ spring-cloud-function-compiler
+ ${spring-cloud-function.version}
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.0
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 1.5.0.BUILD-SNAPSHOT
+
+
+ org.springframework.boot.experimental
+ spring-boot-thin-launcher
+ ${wrapper.version}
+
+
+
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ https://repo.spring.io/release
+
+ false
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ https://repo.spring.io/libs-release-local
+
+ false
+
+
+
+
+
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/main/java/com/example/SampleApplication.java b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/main/java/com/example/SampleApplication.java
new file mode 100644
index 000000000..5dea31394
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/main/java/com/example/SampleApplication.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 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 com.example;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.function.compiler.proxy.ByteCodeLoadingConsumer;
+import org.springframework.cloud.function.compiler.proxy.ByteCodeLoadingFunction;
+import org.springframework.cloud.function.compiler.proxy.ByteCodeLoadingSupplier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
+
+import reactor.core.publisher.Flux;
+
+@SpringBootApplication
+@EnableConfigurationProperties(FunctionProperties.class)
+public class SampleApplication {
+
+ @Autowired
+ private FunctionProperties properties;
+
+ @Bean
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @ConditionalOnProperty(name="function.type", havingValue="supplier")
+ public Supplier> supplier() {
+ return new ByteCodeLoadingSupplier(properties.getResource());
+ }
+
+ @Bean
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @ConditionalOnProperty(name="function.type", havingValue="function")
+ public Function, Flux> function() {
+ return new ByteCodeLoadingFunction(properties.getResource());
+ }
+
+ @Bean
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @ConditionalOnProperty(name="function.type", havingValue="consumer")
+ public Consumer consumer() {
+ return new ByteCodeLoadingConsumer(properties.getResource());
+ }
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(SampleApplication.class, args);
+ }
+}
+
+@ConfigurationProperties("function")
+class FunctionProperties {
+
+ private String type = "function";
+
+ private Resource resource;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+}
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/test/java/com/example/SampleApplicationTests.java b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/test/java/com/example/SampleApplicationTests.java
new file mode 100644
index 000000000..f10a9d892
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-bytecode/src/test/java/com/example/SampleApplicationTests.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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 com.example;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.boot.context.embedded.LocalServerPort;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author Mark Fisher
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
+ properties = "function.resource=file:///tmp/function-registry/functions/uppercase.fun")
+public class SampleApplicationTests {
+
+ @LocalServerPort
+ private int port;
+
+ @Test
+ @Ignore
+ public void uppercase() {
+ assertThat(new TestRestTemplate().postForObject(
+ "http://localhost:" + port + "/function", "{\"value\":\"foo\"}",
+ String.class)).isEqualTo("{\"VALUE\":\"FOO\"}");
+ }
+
+}
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/.jdk8 b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/.jdk8
new file mode 100644
index 000000000..e69de29bb
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/pom.xml b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/pom.xml
new file mode 100644
index 000000000..cf76499ce
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/pom.xml
@@ -0,0 +1,132 @@
+
+
+ 4.0.0
+
+ com.example
+ function-sample-compiler
+ 1.0.0.BUILD-SNAPSHOT
+ jar
+ spring-cloud-function-sample-compiler
+ Spring Cloud Function Lambda Compiling Support
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.0.BUILD-SNAPSHOT
+
+
+
+
+ 1.8
+ 1.0.0.BUILD-SNAPSHOT
+ 0.0.1.BUILD-SNAPSHOT
+ 3.0.4.RELEASE
+
+
+
+
+
+ io.projectreactor
+ reactor-core
+ ${reactor.version}
+
+
+ org.springframework.cloud
+ spring-cloud-function-web
+ ${spring-cloud-function.version}
+
+
+ org.springframework.cloud
+ spring-cloud-function-compiler
+ ${spring-cloud-function.version}
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.0
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 1.5.0.BUILD-SNAPSHOT
+
+
+ org.springframework.boot.experimental
+ spring-boot-thin-launcher
+ ${wrapper.version}
+
+
+
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ https://repo.spring.io/release
+
+ false
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ https://repo.spring.io/libs-release-local
+
+ false
+
+
+
+
+
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/main/java/com/example/SampleApplication.java b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/main/java/com/example/SampleApplication.java
new file mode 100644
index 000000000..0251e67d0
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/main/java/com/example/SampleApplication.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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 com.example;
+
+import java.util.function.Function;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.function.compiler.FunctionCompiler;
+import org.springframework.cloud.function.compiler.proxy.LambdaCompilingFunction;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ByteArrayResource;
+
+import reactor.core.publisher.Flux;
+
+@SpringBootApplication
+@EnableConfigurationProperties(FunctionProperties.class)
+public class SampleApplication {
+
+ @Autowired
+ private FunctionProperties properties;
+
+ @Bean
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Function, Flux> function(FunctionCompiler, ?> compiler) {
+ ByteArrayResource resource = new ByteArrayResource(properties.getLambda().getBytes());
+ return new LambdaCompilingFunction(resource, compiler);
+ }
+
+ @Bean
+ public FunctionCompiler, ?> compiler() {
+ return new FunctionCompiler<>();
+ }
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(SampleApplication.class, args);
+ }
+}
+
+@ConfigurationProperties("function")
+class FunctionProperties {
+
+ private String lambda;
+
+ public String getLambda() {
+ return lambda;
+ }
+
+ public void setLambda(String lambda) {
+ this.lambda = lambda;
+ }
+}
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleApplicationTests.java b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleApplicationTests.java
new file mode 100644
index 000000000..a15b177bd
--- /dev/null
+++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/src/test/java/com/example/SampleApplicationTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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 com.example;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.boot.context.embedded.LocalServerPort;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Mark Fisher
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
+ properties = "function.lambda=f->f.map(s->s.toString().toLowerCase())")
+public class SampleApplicationTests {
+
+ @LocalServerPort
+ private int port;
+
+ @Test
+ public void lowercase() {
+ assertThat(new TestRestTemplate().postForObject(
+ "http://localhost:" + port + "/function", "{\"VALUE\":\"FOO\"}",
+ String.class)).isEqualTo("{\"value\":\"foo\"}");
+ }
+
+}
diff --git a/spring-cloud-function-samples/spring-cloud-function-sample/src/main/java/com/example/SampleApplication.java b/spring-cloud-function-samples/spring-cloud-function-sample/src/main/java/com/example/SampleApplication.java
index d24f78aab..02adb1a52 100644
--- a/spring-cloud-function-samples/spring-cloud-function-sample/src/main/java/com/example/SampleApplication.java
+++ b/spring-cloud-function-samples/spring-cloud-function-sample/src/main/java/com/example/SampleApplication.java
@@ -1,18 +1,19 @@
/*
- * Copyright 2013-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.
-*/
+ * Copyright 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 com.example;
import java.util.function.Function;
@@ -21,7 +22,7 @@ import java.util.function.Supplier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.function.compiler.FunctionCompiler;
-import org.springframework.cloud.function.compiler.LambdaCompilingFunction;
+import org.springframework.cloud.function.compiler.proxy.LambdaCompilingFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ByteArrayResource;
diff --git a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/StreamConfiguration.java b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/StreamConfiguration.java
index db382376d..b25014fb1 100644
--- a/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/StreamConfiguration.java
+++ b/spring-cloud-function-stream/src/main/java/org/springframework/cloud/function/stream/StreamConfiguration.java
@@ -43,6 +43,7 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
@@ -134,6 +135,9 @@ public class StreamConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String functionName = context.getEnvironment().getProperty("function.name");
+ if (!StringUtils.hasText(functionName)) {
+ return ConditionOutcome.noMatch("no function name available");
+ }
Class> beanType = context.getBeanFactory().getType(functionName);
if (type.isAssignableFrom(beanType)) {
return ConditionOutcome.match(String.format("bean '%s' is a %s", functionName, type));
diff --git a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionController.java b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionController.java
index 117043345..9bab7aabd 100644
--- a/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionController.java
+++ b/spring-cloud-function-web/src/main/java/org/springframework/cloud/function/web/FunctionController.java
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.cloud.function.web;
+import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +33,7 @@ import reactor.core.publisher.Flux;
/**
* @author Dave Syer
- *
+ * @author Mark Fisher
*/
@RestController
@ConditionalOnClass(RestController.class)
@@ -51,9 +53,14 @@ public class FunctionController {
public Flux function(@PathVariable String name,
@RequestBody Flux body) {
Function