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-sample spring-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 function = functions.lookupFunction(name); - @SuppressWarnings("unchecked") - Flux result = (Flux) function.apply(body); - return debug ? result.log() : result; + if (function != null) { + @SuppressWarnings("unchecked") + Flux result = (Flux) function.apply(body); + return debug ? result.log() : result; + } + Consumer consumer = functions.lookupConsumer(name); + body.subscribe(consumer::accept); + return null; } @GetMapping(path = "/{name}") @@ -62,5 +69,4 @@ public class FunctionController { Flux result = (Flux) functions.lookupSupplier(name).get(); return debug ? result.log() : result; } - }