From f20cff0fc8ae49247c6511c57104ff5a46b3aa5e Mon Sep 17 00:00:00 2001 From: markfisher Date: Fri, 3 Feb 2017 16:22:48 -0500 Subject: [PATCH] added FunctionProxyApplicationListener added support for lambda compiling Supplier and Consumer proxies --- spring-cloud-function-compiler/pom.xml | 4 + .../FunctionProxyApplicationListener.java | 115 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + .../pom.xml | 11 -- .../java/com/example/SampleApplication.java | 41 ------- .../com/example/SampleApplicationTests.java | 6 +- 6 files changed, 124 insertions(+), 55 deletions(-) create mode 100644 spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java create mode 100644 spring-cloud-function-compiler/src/main/resources/META-INF/spring.factories diff --git a/spring-cloud-function-compiler/pom.xml b/spring-cloud-function-compiler/pom.xml index ceb39813e..8fdeb89f0 100644 --- a/spring-cloud-function-compiler/pom.xml +++ b/spring-cloud-function-compiler/pom.xml @@ -37,6 +37,10 @@ commons-collections commons-collections + + org.springframework + spring-context + org.springframework.boot spring-boot-starter-web 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 new file mode 100644 index 000000000..b408d318e --- /dev/null +++ b/spring-cloud-function-compiler/src/main/java/org/springframework/cloud/function/compiler/config/FunctionProxyApplicationListener.java @@ -0,0 +1,115 @@ +/* + * 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.config; + +import java.util.Map; + +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.bind.PropertySourcesBinder; +import org.springframework.boot.context.event.ApplicationPreparedEvent; +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.compiler.proxy.ByteCodeLoadingConsumer; +import org.springframework.cloud.function.compiler.proxy.ByteCodeLoadingFunction; +import org.springframework.cloud.function.compiler.proxy.ByteCodeLoadingSupplier; +import org.springframework.cloud.function.compiler.proxy.LambdaCompilingConsumer; +import org.springframework.cloud.function.compiler.proxy.LambdaCompilingFunction; +import org.springframework.cloud.function.compiler.proxy.LambdaCompilingSupplier; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; + +/** + * @author Mark Fisher + */ +public class FunctionProxyApplicationListener implements ApplicationListener { + + private final SupplierCompiler supplierCompiler = new SupplierCompiler<>(); + + private final FunctionCompiler functionCompiler = new FunctionCompiler<>(); + + private final ConsumerCompiler consumerCompiler = new ConsumerCompiler<>(); + + @Override + public void onApplicationEvent(ApplicationPreparedEvent event) { + ConfigurableApplicationContext context = event.getApplicationContext(); + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + PropertySourcesBinder binder = new PropertySourcesBinder(context.getEnvironment()); + Map extracted = binder.extractAll("spring.cloud.function.proxy"); + for (Map.Entry entry : extracted.entrySet()) { + String name = entry.getKey(); + @SuppressWarnings("unchecked") + Map properties = (Map) entry.getValue(); + String type = (properties.get("type") != null) ? properties.get("type") : "function"; + String resource = properties.get("resource"); + String lambda = properties.get("lambda"); + if (!(null == resource ^ null == lambda)) { + throw new IllegalArgumentException("Exactly one of 'resource' or 'lambda' is required for a Function proxy"); + } + if (resource != null) { + registerByteCodeLoadingProxy(name, type, context.getResource(resource), beanFactory); + } + else { + registerLambdaCompilingProxy(name, type, lambda, beanFactory); + } + } + } + + private void registerByteCodeLoadingProxy(String name, String type, Resource resource, DefaultListableBeanFactory beanFactory) { + Class proxyClass = null; + if ("supplier".equals(type.toLowerCase())) { + proxyClass = ByteCodeLoadingSupplier.class; + } + else if ("consumer".equals(type.toLowerCase())) { + proxyClass = ByteCodeLoadingConsumer.class; + } + else { + proxyClass = ByteCodeLoadingFunction.class; + } + RootBeanDefinition beanDefinition = new RootBeanDefinition(proxyClass); + ConstructorArgumentValues args = new ConstructorArgumentValues(); + args.addGenericArgumentValue(resource); + beanDefinition.setConstructorArgumentValues(args); + beanFactory.registerBeanDefinition(name, beanDefinition); + } + + private void registerLambdaCompilingProxy(String name, String type, String lambda, DefaultListableBeanFactory beanFactory) { + Resource resource = new ByteArrayResource(lambda.getBytes()); + ConstructorArgumentValues args = new ConstructorArgumentValues(); + args.addGenericArgumentValue(resource); + Class proxyClass = null; + if ("supplier".equals(type.toLowerCase())) { + proxyClass = LambdaCompilingSupplier.class; + args.addGenericArgumentValue(this.supplierCompiler); + } + else if ("consumer".equals(type.toLowerCase())) { + proxyClass = LambdaCompilingConsumer.class; + args.addGenericArgumentValue(this.consumerCompiler); + } + else { + proxyClass = LambdaCompilingFunction.class; + args.addGenericArgumentValue(this.functionCompiler); + } + RootBeanDefinition beanDefinition = new RootBeanDefinition(proxyClass); + beanDefinition.setConstructorArgumentValues(args); + beanFactory.registerBeanDefinition(name, beanDefinition); + } +} diff --git a/spring-cloud-function-compiler/src/main/resources/META-INF/spring.factories b/spring-cloud-function-compiler/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..0f7f11139 --- /dev/null +++ b/spring-cloud-function-compiler/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ +org.springframework.cloud.function.compiler.config.FunctionProxyApplicationListener 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 index a3f259a8d..30f715a23 100644 --- a/spring-cloud-function-samples/spring-cloud-function-sample-compiler/pom.xml +++ b/spring-cloud-function-samples/spring-cloud-function-sample-compiler/pom.xml @@ -25,12 +25,6 @@ - - - io.projectreactor - reactor-core - ${reactor.version} - org.springframework.cloud spring-cloud-function-web @@ -41,11 +35,6 @@ spring-cloud-function-compiler ${spring-cloud-function.version} - - org.springframework.boot - spring-boot-configuration-processor - true - 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 index 0251e67d0..ce9fe95ff 100644 --- 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 @@ -16,54 +16,13 @@ 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 index a15b177bd..9466793c2 100644 --- 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 @@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, - properties = "function.lambda=f->f.map(s->s.toString().toLowerCase())") + properties = "spring.cloud.function.proxy.test.lambda=f->f.map(s->s+\"!!!\")") public class SampleApplicationTests { @LocalServerPort @@ -41,8 +41,8 @@ public class SampleApplicationTests { @Test public void lowercase() { assertThat(new TestRestTemplate().postForObject( - "http://localhost:" + port + "/function", "{\"VALUE\":\"FOO\"}", - String.class)).isEqualTo("{\"value\":\"foo\"}"); + "http://localhost:" + port + "/test", "it works", + String.class)).isEqualTo("it works!!!"); } }