Renamed deployer-new to deployer, removed old deployer

This commit is contained in:
Oleg Zhurakousky
2019-08-12 17:26:22 +02:00
parent 17ef359b60
commit ed2fa9bb7a
75 changed files with 462 additions and 463 deletions

View File

View File

@@ -0,0 +1,21 @@
Spring Cloud Function Deployer is an library for building apps that can deploy functions packaged as jars. It can deploy a basic Spring Cloud Function app from a jar with locally cached dependencies in about 500ms (compared to 1500ms for the same application launched from cold). It can be used in a pool as a "warm" JVM to deploy functions quicker than they could be started from scratch. Example usage:
```java
@SpringBootApplication
@EnableFunctionDeployer
public class FunctionApplication {
public static void main(String[] args) throws IOException {
new ApplicationBootstrap().run(FunctionApplication.class, args);
}
}
```
There is a main class in the jar that alread looks like this. You can use it like that or you can create your own copy if you want to customize it. The `ApplicationBootstrap` is a utility that replaces `SpringApplication`, creating a class loader hierarchy that works with the function configuration. It needs to be launched with configuration for the `FunctionProperties`:
| Option | Description |
|--------|----------------------|
| `function.location` | Mandatory archive location(s) for building the classpath of the function. |
| `function.bean` | Mandatory bean class or name (if `function.main` is provided) to create the function. If multi-valued, the function is composed (outputs piped to inputs) |
| `function.main` | The main `@SpringBootApplication` to launch (optional). |

View File

@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-function-deployer-old</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-function-deployer</name>
<description>Spring Cloud Function Web Support</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-parent</artifactId>
<version>3.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<wrapper.version>1.0.10.RELEASE</wrapper.version>
<spring.cloud.deployer.version>2.0.2.BUILD-SNAPSHOT</spring.cloud.deployer.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-maven</artifactId>
<version>${spring.cloud.deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-support</artifactId>
<version>${spring.cloud.deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>${wrapper.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<localRepositoryPath>${project.build.directory}/local-repo
</localRepositoryPath>
</configuration>
<executions>
<execution>
<id>prepare-test</id>
<phase>test-compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<cloneProjectsTo>${project.build.directory}/it
</cloneProjectsTo>
<settingsFile>src/it/settings.xml</settingsFile>
<addTestClassPath>true</addTestClassPath>
<streamLogs>true</streamLogs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-invoker-plugin
</artifactId>
<versionRange>
[1.10,)
</versionRange>
<goals>
<goal>run</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>invoke</id>
<activation>
<file>
<missing>target/it/flux/pom.xml</missing>
</file>
</activation>
<properties>
<skip.invoke>false</skip.invoke>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>flux-sample</artifactId>
<version>1.0.0.RC1</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.M5</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud-function.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
<wrapper.version>1.0.17.RELEASE</wrapper.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>${spring-cloud-function.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<classifier>exec</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- <repositories> -->
<!-- <repository> -->
<!-- <id>spring-snapshots</id> -->
<!-- <name>Spring Snapshots</name> -->
<!-- <url>https://repo.spring.io/libs-snapshot-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>true</enabled> -->
<!-- </snapshots> -->
<!-- <releases> -->
<!-- <enabled>false</enabled> -->
<!-- </releases> -->
<!-- </repository> -->
<!-- <repository> -->
<!-- <id>spring-milestones</id> -->
<!-- <name>Spring Milestones</name> -->
<!-- <url>https://repo.spring.io/libs-milestone-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </repository> -->
<!-- <repository> -->
<!-- <id>spring-releases</id> -->
<!-- <name>Spring Releases</name> -->
<!-- <url>https://repo.spring.io/release</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </repository> -->
<!-- </repositories> -->
<!-- <pluginRepositories> -->
<!-- <pluginRepository> -->
<!-- <id>spring-snapshots</id> -->
<!-- <name>Spring Snapshots</name> -->
<!-- <url>https://repo.spring.io/libs-snapshot-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>true</enabled> -->
<!-- </snapshots> -->
<!-- <releases> -->
<!-- <enabled>false</enabled> -->
<!-- </releases> -->
<!-- </pluginRepository> -->
<!-- <pluginRepository> -->
<!-- <id>spring-milestones</id> -->
<!-- <name>Spring Milestones</name> -->
<!-- <url>https://repo.spring.io/libs-milestone-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </pluginRepository> -->
<!-- <pluginRepository> -->
<!-- <id>spring-releases</id> -->
<!-- <name>Spring Releases</name> -->
<!-- <url>https://repo.spring.io/libs-release-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </pluginRepository> -->
<!-- </pluginRepositories> -->
</project>

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2019 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
*
* https://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.functions;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* @author Dave Syer
*/
@SpringBootApplication
public class FunctionApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(FunctionApp.class, args);
}
@Bean
public Function<Flux<Foo>, Flux<Foo>> foos() {
return flux -> flux.map(value -> new Foo(value.getValue().toUpperCase()));
}
}
class Foo {
private String value;
public Foo() {
}
public Foo(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Foo [value=" + this.value + "]";
}
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<profiles>
<profile>
<id>it-repo</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>function-sample</artifactId>
<version>1.0.0.M1</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.M5</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud-function.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
<wrapper.version>1.0.17.RELEASE</wrapper.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>${spring-cloud-function.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<classifier>exec</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- <repositories> -->
<!-- <repository> -->
<!-- <id>spring-snapshots</id> -->
<!-- <name>Spring Snapshots</name> -->
<!-- <url>https://repo.spring.io/libs-snapshot-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>true</enabled> -->
<!-- </snapshots> -->
<!-- <releases> -->
<!-- <enabled>false</enabled> -->
<!-- </releases> -->
<!-- </repository> -->
<!-- <repository> -->
<!-- <id>spring-milestones</id> -->
<!-- <name>Spring Milestones</name> -->
<!-- <url>https://repo.spring.io/libs-milestone-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </repository> -->
<!-- <repository> -->
<!-- <id>spring-releases</id> -->
<!-- <name>Spring Releases</name> -->
<!-- <url>https://repo.spring.io/release</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </repository> -->
<!-- </repositories> -->
<!-- <pluginRepositories> -->
<!-- <pluginRepository> -->
<!-- <id>spring-snapshots</id> -->
<!-- <name>Spring Snapshots</name> -->
<!-- <url>https://repo.spring.io/libs-snapshot-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>true</enabled> -->
<!-- </snapshots> -->
<!-- <releases> -->
<!-- <enabled>false</enabled> -->
<!-- </releases> -->
<!-- </pluginRepository> -->
<!-- <pluginRepository> -->
<!-- <id>spring-milestones</id> -->
<!-- <name>Spring Milestones</name> -->
<!-- <url>https://repo.spring.io/libs-milestone-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </pluginRepository> -->
<!-- <pluginRepository> -->
<!-- <id>spring-releases</id> -->
<!-- <name>Spring Releases</name> -->
<!-- <url>https://repo.spring.io/libs-release-local</url> -->
<!-- <snapshots> -->
<!-- <enabled>false</enabled> -->
<!-- </snapshots> -->
<!-- </pluginRepository> -->
<!-- </pluginRepositories> -->
</project>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2019 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
*
* https://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.functions;
import java.util.function.Consumer;
public class DoubleLogger implements Consumer<Integer> {
@Override
public void accept(Integer i) {
System.out.println(2 * i);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2019 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
*
* https://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.functions;
import java.util.function.Supplier;
/**
* @author Eric Bottard
*/
public class Emitter implements Supplier<String> {
private int i = 0;
private String[] values = {"one", "two", "three", "four"};
@Override
public String get() {
return values[i++ % values.length];
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2019 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
*
* https://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.functions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* @author Dave Syer
*/
@SpringBootApplication
public class FunctionApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(FunctionApp.class, args);
}
@Bean
public DoubleLogger myDoubler() {
return new DoubleLogger();
}
@Bean
public Emitter myEmitter() {
return new Emitter();
}
@Bean
public LengthCounter myCounter() {
return new LengthCounter();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2012-2019 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
*
* https://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.functions;
import java.util.function.Function;
/**
* @author Eric Bottard
*/
public class LengthCounter implements Function<String, Integer> {
@Override
public Integer apply(String string) {
return string.length();
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.util.StringUtils;
/**
* Utility class to launch a Spring Boot application (optionally) in an isolated class
* loader. The class loader is created in such a way that it is mostly a copy of the
* current class loader (i.e. the one that loaded this class), but has a parent containing
* reactor-core (if present). It can then share the reactor dependency with other class
* loaders that the app itself creates, without any other classes being shared, other than
* the core JDK.
*
* @author Mark Fisher
* @author Dave Syer
*/
public class ApplicationBootstrap {
private static Log logger = LogFactory.getLog(ApplicationBootstrap.class);
private ApplicationRunner runner;
private URLClassLoader classLoader;
private static boolean isolated(String[] args) {
for (String arg : args) {
if (arg.equals("--function.runner.isolated=false")) {
return false;
}
}
return true;
}
/**
* Run the provided main class as a Spring Boot application with the provided command
* line arguments.
* @param mainClass the main class
* @param args the command line arguments
*/
public void run(Class<?> mainClass, String... args) {
if (ApplicationBootstrap.isolated(args)) {
runner(mainClass).run(args);
}
else {
SpringApplication.run(mainClass, args);
}
}
/**
* Clean up the resources used by this instance, if any. Called automatically on a
* runtime shutdown hook.
*/
public void close() {
if (this.runner != null) {
this.runner.close();
this.runner = null;
}
if (this.classLoader != null) {
try {
this.classLoader.close();
}
catch (IOException e) {
throw new IllegalStateException("Cannot close ClassLoader", e);
}
finally {
this.classLoader = null;
}
}
}
public ApplicationRunner getRunner() {
return this.runner;
}
private ApplicationRunner runner(Class<?> mainClass) {
if (this.runner == null) {
synchronized (this) {
if (this.runner == null) {
this.classLoader = createClassLoader(mainClass);
this.runner = new ApplicationRunner(this.classLoader,
mainClass.getName());
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}
}
}
return this.runner;
}
private URLClassLoader createClassLoader(Class<?> mainClass) {
URL[] urls = findClassPath(mainClass);
if (urls.length == 1) {
URL[] classpath = extractClasspath(urls[0]);
if (classpath != null) {
urls = classpath;
}
}
List<URL> child = new ArrayList<>();
List<URL> parent = new ArrayList<>();
for (URL url : urls) {
child.add(url);
}
for (URL url : urls) {
if (isRoot(StringUtils.getFilename(clean(url.toString())))) {
parent.add(url);
child.remove(url);
}
}
logger.debug("Parent: " + parent);
logger.debug("Child: " + child);
ClassLoader base = getClass().getClassLoader();
if (!parent.isEmpty()) {
base = new URLClassLoader(parent.toArray(new URL[0]), base.getParent());
}
return new URLClassLoader(child.toArray(new URL[0]), base);
}
private URL[] findClassPath(Class<?> mainClass) {
ClassLoader base = mainClass.getClassLoader();
if (!(base instanceof URLClassLoader)) {
try {
// Guess the classpath, based on where we can resolve existing resources
List<URL> list = Collections
.list(mainClass.getClassLoader().getResources("META-INF"));
List<URL> result = new ArrayList<>();
result.add(mainClass.getProtectionDomain().getCodeSource().getLocation());
for (URL url : list) {
String path = url.toString();
path = path.substring(0, path.length() - "/META-INF".length()) + "/";
result.add(new URL(path));
}
return result.toArray(new URL[result.size()]);
}
catch (IOException e) {
throw new IllegalStateException("Cannot find class path", e);
}
}
else {
@SuppressWarnings("resource")
URLClassLoader urlClassLoader = (URLClassLoader) base;
return urlClassLoader.getURLs();
}
}
private boolean isRoot(String file) {
return file.startsWith("reactor-core") || file.startsWith("reactive-streams");
}
private String clean(String jar) {
// This works with fat jars like Spring Boot where the path elements look like
// jar:file:...something.jar!/.
return jar.endsWith("!/") ? jar.substring(0, jar.length() - 2) : jar;
}
private URL[] extractClasspath(URL url) {
// This works for a jar indirection like in surefire and IntelliJ
if (url.toString().endsWith(".jar")) {
JarFile jar;
try {
jar = new JarFile(new File(url.toURI()));
String path = jar.getManifest().getMainAttributes()
.getValue("Class-Path");
if (path != null) {
List<URL> result = new ArrayList<>();
for (String element : path.split(" ")) {
result.add(new URL(element));
}
return result.toArray(new URL[0]);
}
}
catch (Exception e) {
}
}
return null;
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.LiveBeansView;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeLocator;
import org.springframework.util.ClassUtils;
/**
* Driver class for running a Spring Boot application via an isolated classpath.
* Initialize an instance of this class with the class loader to be used and the name of
* the main class (usually a <code>@SpringBootApplication</code>), and then
* {@link #run(String...)} it, cleaning up with a call to {@link #close()}.
*
* @author Dave Syer
* @author Oleg Zhurakousky
*/
public class ApplicationRunner {
private static Log logger = LogFactory.getLog(ApplicationRunner.class);
private final ClassLoader classLoader;
private final String source;
private final SpelParserConfiguration config;
private final StandardTypeLocator typeLocator;
private StandardEvaluationContext app;
public ApplicationRunner(ClassLoader classLoader, String source) {
this.classLoader = classLoader;
this.source = source;
this.config = new SpelParserConfiguration(null, this.classLoader);
this.typeLocator = new StandardTypeLocator(this.classLoader);
}
public void run(String... args) {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
ClassUtils.overrideThreadContextClassLoader(this.classLoader);
Class<?> cls = this.classLoader.loadClass(ContextRunner.class.getName());
this.app = new StandardEvaluationContext(
cls.getDeclaredConstructor().newInstance());
this.app.setTypeLocator(new StandardTypeLocator(this.classLoader));
runContext(this.source, defaultProperties(UUID.randomUUID().toString()),
args);
}
catch (Exception e) {
logger.error("Cannot deploy", e);
}
finally {
ClassUtils.overrideThreadContextClassLoader(contextLoader);
}
RuntimeException e = getError();
if (e != null) {
throw e;
}
}
private Map<String, String> defaultProperties(String id) {
Map<String, String> map = new HashMap<>();
map.put(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME, "function-deployer-" + id);
map.put("spring.jmx.default-domain", "function-deployer-" + id);
map.put("spring.jmx.enabled", "false");
return map;
}
public Object getBean(String name) {
if (this.app != null) {
if (containsBeanByName(name)) {
return getBeanByName(name);
}
try {
return getBeanByType(name);
}
catch (Exception e) {
// not there
}
}
return null;
}
private boolean containsBeanByName(String name) {
Expression parsed = new SpelExpressionParser()
.parseExpression("context.containsBean(\"" + name + "\")");
return parsed.getValue(this.app, Boolean.class);
}
private Object getBeanByName(String name) {
Expression parsed = new SpelExpressionParser()
.parseExpression("context.getBean(\"" + name + "\")");
return parsed.getValue(this.app);
}
private Object getBeanByType(String name) {
Expression parsed = new SpelExpressionParser()
.parseExpression("context.getBean(T(" + name + "))");
return parsed.getValue(this.app);
}
public boolean containsBean(String name) {
if (this.app != null) {
if (containsBeanByName(name)) {
return true;
}
Expression parsed = new SpelExpressionParser()
.parseExpression("context.getBeansOfType(T(" + name + "))");
try {
@SuppressWarnings("unchecked")
Map<String, Object> beans = (Map<String, Object>) parsed
.getValue(this.app);
return !beans.isEmpty();
}
catch (Exception e) {
}
}
return false;
}
/**
* List the bean names in the application context for a given type (by its fully
* qualified name).
* @param type the name of the type (Class)
* @return the bean names of that type
*/
public Set<String> getBeanNames(String type) {
if (this.app != null) {
Expression parsed = new SpelExpressionParser()
.parseExpression("context.getBeansOfType(T(" + type + "))");
try {
@SuppressWarnings("unchecked")
Map<String, Object> beans = (Map<String, Object>) parsed
.getValue(this.app);
return beans.keySet();
}
catch (Exception e) {
}
}
return Collections.emptySet();
}
public Object evaluate(String expression, Object root, Object... attrs) {
Expression parsed = new SpelExpressionParser(this.config)
.parseExpression(expression);
StandardEvaluationContext context = new StandardEvaluationContext(root);
context.setTypeLocator(this.typeLocator);
if (attrs.length % 2 != 0) {
throw new IllegalArgumentException(
"Context attributes must be name, value pairs");
}
for (int i = 0; i < attrs.length / 2; i++) {
String name = (String) attrs[2 * i];
Object value = attrs[2 * i + 1];
context.setVariable(name, value);
}
return parsed.getValue(context);
}
public boolean isRunning() {
if (this.app == null) {
return false;
}
Expression parsed = new SpelExpressionParser()
.parseExpression("context.isRunning()");
return parsed.getValue(this.app, Boolean.class);
}
@PreDestroy
public void close() {
closeContext();
}
private RuntimeException getError() {
if (this.app == null) {
return null;
}
Expression parsed = new SpelExpressionParser().parseExpression("error");
Throwable e = parsed.getValue(this.app, Throwable.class);
if (e == null) {
return null;
}
if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new IllegalStateException("Cannot launch", e);
}
private void runContext(String mainClass, Map<String, String> properties,
String... args) {
Expression parsed = new SpelExpressionParser()
.parseExpression("run(#main,#properties,#args)");
StandardEvaluationContext context = this.app;
context.setVariable("main", mainClass);
context.setVariable("properties", properties);
context.setVariable("args", args);
parsed.getValue(context);
}
private void closeContext() {
if (this.app != null) {
Expression parsed = new SpelExpressionParser().parseExpression("close()");
parsed.getValue(this.app);
this.app = null;
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Utility class for starting a Spring Boot application in a separate thread. Best used
* from an isolated class loader, e.g. through {@link ApplicationRunner}.
*
* @author Dave Syer
*/
public class ContextRunner {
private ConfigurableApplicationContext context;
private Thread runThread;
private volatile boolean running = false;
private Throwable error;
private long timeout = 120000;
private static boolean isFunctional(StandardEnvironment environment) {
if (!ClassUtils.isPresent(
"org.springframework.cloud.function.context.FunctionalSpringApplication",
null)) {
return false;
}
return environment.resolvePlaceholders("${spring.functional.enabled:true}")
.equals("true");
}
public void run(final String source, final Map<String, Object> properties,
final String... args) {
// Run in new thread to ensure that the context classloader is setup
this.runThread = new Thread(new Runnable() {
@Override
public void run() {
try {
resetUrlHandler();
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new MapPropertySource("appDeployer", properties));
if (args != null && args.length > 0) {
environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource("args", args));
}
ContextRunner.this.running = true;
Class<?> sourceClass = ClassUtils.resolveClassName(source, null);
SpringApplication builder = builder(sourceClass, environment);
ContextRunner.this.context = builder.run(args);
}
catch (Throwable ex) {
ContextRunner.this.error = ex;
}
}
});
this.runThread.start();
try {
this.runThread.join(this.timeout);
this.running = this.context != null && this.context.isRunning();
}
catch (InterruptedException e) {
this.running = false;
Thread.currentThread().interrupt();
}
}
public void close() {
if (this.context != null) {
this.context.close();
resetUrlHandler();
}
// TODO: JDBC leak protection?
this.running = false;
this.runThread.setContextClassLoader(null);
this.runThread = null;
}
public ConfigurableApplicationContext getContext() {
return this.context;
}
private void resetUrlHandler() {
if (ClassUtils.isPresent(
"org.apache.catalina.webresources.TomcatURLStreamHandlerFactory", null)) {
setField(ClassUtils.resolveClassName(
"org.apache.catalina.webresources.TomcatURLStreamHandlerFactory",
null), "instance", null);
setField(URL.class, "factory", null);
}
}
private void setField(Class<?> type, String name, Object value) {
Field field = ReflectionUtils.findField(type, name);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, value);
}
public boolean isRunning() {
return this.running;
}
public Throwable getError() {
return this.error;
}
private SpringApplication builder(Class<?> type, StandardEnvironment environment) {
SpringApplication application;
if (!isFunctional(environment)) {
application = new SpringApplication(type);
}
else {
application = FunctionalSpringApplicationCreator.create(type);
}
application.setEnvironment(environment);
application.setRegisterShutdownHook(false);
return application;
}
private static class FunctionalSpringApplicationCreator {
public static SpringApplication create(Class<?> type) {
return new FunctionalSpringApplication(type);
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Annotation to be used on a Spring Boot application if it wants to deploy a jar file
* containing a function definition.
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(FunctionDeployerConfiguration.class)
public @interface EnableFunctionDeployer {
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.io.IOException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// @checkstyle:off
/**
* @author Mark Fisher
* @author Dave Syer
*/
@SpringBootApplication
@EnableFunctionDeployer
public class FunctionApplication {
public static void main(String[] args) throws IOException {
new ApplicationBootstrap().run(FunctionApplication.class, args);
}
}
// @checkstyle:on

View File

@@ -0,0 +1,658 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.loader.JarLauncher;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.ExplodedArchive;
import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.boot.system.JavaVersion;
import org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionRegistry;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.core.FluxFunction;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
*
* Registers beans that will be picked up by spring-cloud-function-context magic. Sets up
* infrastructure capable of instantiating a "functional" bean (whether Supplier, Function
* or Consumer) loaded dynamically according to {@link FunctionProperties}.
*
* <p>
* Resolves jar location provided by the user using a flexible ResourceLoader.
* </p>
*
* @author Eric Bottard
* @author Mark Fisher
* @author Dave Syer
* @author Oleg Zhurakousky
*/
@Configuration
class FunctionCreatorConfiguration {
private static Log logger = LogFactory.getLog(FunctionCreatorConfiguration.class);
@Autowired
private FunctionRegistry registry;
@Autowired
private FunctionProperties properties;
@Autowired
private DelegatingResourceLoader delegatingResourceLoader;
@Autowired
private ConfigurableApplicationContext context;
private BeanCreatorClassLoader functionClassLoader;
private BeanCreator creator;
/**
* Registers a function for each of the function classes passed into the
* {@link FunctionProperties}. They are named sequentially "function0", "function1",
* etc. The instances are created in an isolated class loader, so the jar they are
* packed in has to define all the dependencies (except core JDK).
*/
@PostConstruct
public void init() {
URL[] urls = Arrays.stream(this.properties.getLocation())
.flatMap(toResourceURL(this.delegatingResourceLoader))
.toArray(URL[]::new);
URL[] roots = Arrays.stream(this.properties.getLocation()).map(this::toUrl)
.toArray(URL[]::new);
try {
logger.info("Locating function from "
+ Arrays.asList(this.properties.getLocation()));
this.creator = new BeanCreator(roots, urls);
this.creator.run(this.properties.getMain());
Arrays.stream(functionNames()).map(this.creator::create).sequential()
.forEach(this.creator::register);
if (this.properties.getName().contains("|")) {
// A composite function has to be explicitly registered before it is
// looked up because we are using the SingleEntryFunctionRegistry
// Object o = this.registry.lookup(Consumer.class, this.properties.getName());
// o = this.registry.lookup(Function.class, this.properties.getName());
// o = this.registry.lookup(Supplier.class, this.properties.getName());
// System.out.println();
}
}
catch (Exception e) {
throw new IllegalStateException("Cannot create functions", e);
}
}
private URL toUrl(String url) {
if (url.equals("app:classpath")) {
return urls()[0];
}
try {
return new URL(url);
}
catch (MalformedURLException e) {
throw new UncheckedIOException(e);
}
}
private String[] functionNames() {
if (this.properties.getBean() != null && this.properties.getBean().length > 0) {
return this.properties.getBean();
}
return this.creator.getFunctionNames();
}
@PreDestroy
public void close() {
if (this.creator != null) {
this.creator.close();
}
if (this.functionClassLoader != null) {
try {
this.functionClassLoader.close();
this.functionClassLoader = null;
Runtime.getRuntime().gc();
}
catch (IOException e) {
throw new IllegalStateException("Cannot close function class loader", e);
}
}
}
private Function<String, Stream<URL>> toResourceURL(
DelegatingResourceLoader resourceLoader) {
return l -> {
if (l.equals("app:classpath")) {
return Stream.of(urls());
}
try {
return Stream.of(resourceLoader.getResource(l).getFile().toURI().toURL());
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
private URL[] urls() {
if (getClass().getClassLoader() instanceof URLClassLoader) {
return ((URLClassLoader) getClass().getClassLoader()).getURLs();
}
// Want to load these the test types in a disposable classloader:
List<URL> urls = new ArrayList<>();
String jcp = System.getProperty("java.class.path");
StringTokenizer jcpEntries = new StringTokenizer(jcp, File.pathSeparator);
while (jcpEntries.hasMoreTokens()) {
String pathEntry = jcpEntries.nextToken();
try {
urls.add(new File(pathEntry).toURI().toURL());
}
catch (MalformedURLException e) {
}
}
return urls.toArray(new URL[urls.size()]);
}
private static final class BeanCreatorClassLoader extends URLClassLoader {
private BeanCreatorClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
if (name.startsWith("javax.") && JavaVersion.getJavaVersion()
.isEqualOrNewerThan(JavaVersion.NINE)) {
return getClass().getClassLoader().loadClass(name);
}
return super.loadClass(name, resolve);
}
catch (ClassNotFoundException e) {
if (name.contains(ContextRunner.class.getName())
|| name.contains(PostConstruct.class.getName())) {
// Special case for the ContextRunner. We can re-use the bytes for it,
// and the function jar doesn't have to include them since it is only
// used here.
byte[] bytes;
try {
bytes = StreamUtils.copyToByteArray(
getClass().getClassLoader().getResourceAsStream(
ClassUtils.convertClassNameToResourcePath(name)
+ ".class"));
return defineClass(name, bytes, 0, bytes.length);
}
catch (IOException ex) {
throw new ClassNotFoundException(
"Cannot find runner class: " + name, ex);
}
}
throw e;
}
}
}
@Configuration
protected static class SingleEntryConfiguration implements BeanPostProcessor {
@Autowired
private Environment env;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FunctionRegistry) {
String name = FunctionProperties
.functionName(this.env.getProperty("function.bean", ""));
if (name.contains("|")) {
// A single composite function with an empty name
bean = new SingleEntryFunctionRegistry((FunctionRegistry) bean, name);
}
}
return bean;
}
}
private class ComputeLauncher extends JarLauncher {
ComputeLauncher(Archive archive) {
super(archive);
}
@Override
public String getMainClass() throws Exception {
Manifest manifest = getArchive().getManifest();
String mainClass = null;
if (manifest != null) {
String functionClass = manifest.getMainAttributes()
.getValue("Function-Class");
if (StringUtils.hasText(functionClass) && ObjectUtils.isEmpty(
FunctionCreatorConfiguration.this.properties.getBean())) {
FunctionCreatorConfiguration.this.properties
.setBean(new String[] { functionClass });
}
mainClass = manifest.getMainAttributes().getValue("Start-Class");
if (mainClass == null
// Not surefire or IntelliJ
&& !getArchive().getUrl().toString().endsWith(".jar!/")) {
// Not a Spring Boot jar but it might have a "main" class
mainClass = manifest.getMainAttributes().getValue("Main-Class");
}
}
return mainClass;
}
public URL[] getClassLoaderUrls() throws Exception {
List<Archive> archives = getClassPathArchives();
if (archives.isEmpty()) {
URL url = getArchive().getUrl();
if (url.toString().contains(".jar")) { // Surefire or IntelliJ?
URL[] classpath = extractClasspath(url.toString());
if (classpath != null) {
return classpath;
}
}
return new URL[] { getArchive().getUrl() };
}
return archives.stream().map(archive -> {
try {
return archive.getUrl();
}
catch (MalformedURLException e) {
throw new IllegalStateException("Bad URL: " + archive, e);
}
}).toArray(URL[]::new);
}
private URL[] extractClasspath(String url) {
// This works for a jar indirection like in surefire and IntelliJ
if (url.endsWith(".jar!/")) {
url = url.substring(0, url.length() - "!/".length());
if (url.startsWith("jar:")) {
url = url.substring("jar:".length());
}
if (url.startsWith("file:")) {
url = url.substring("file:".length());
}
}
if (url.endsWith(".jar")) {
JarFile jar;
try {
jar = new JarFile(new File(url));
String path = jar.getManifest().getMainAttributes()
.getValue("Class-Path");
if (path != null) {
List<URL> result = new ArrayList<>();
for (String element : path.split(" ")) {
result.add(new URL(element));
}
return result.toArray(new URL[0]);
}
}
catch (Exception e) {
}
}
return null;
}
}
/**
* Encapsulates the bean and spring application context creation concerns for
* functions. Creates a single application context if <code>run()</code> is called
* with a non-null main class, and then uses it to lookup a function (by name and then
* by type).
*/
private class BeanCreator {
private AtomicInteger counter = new AtomicInteger(0);
private ApplicationRunner runner;
private String defaultMain;
BeanCreator(URL[] roots, URL[] urls) {
FunctionCreatorConfiguration.this.functionClassLoader = new BeanCreatorClassLoader(
expand(urls), getParent());
this.defaultMain = findMain(roots);
}
private ClassLoader getParent() {
ClassLoader loader = getClass().getClassLoader();
loader = loader.getParent();
ClassLoader parent = loader;
while (loader.getParent() != null) {
// If launched from a fat jar with spring.factories skip this parent level
// (which was added by the JarLauncher).
if (loader.getResource("META-INF/spring.factories") != null) {
parent = loader.getParent();
}
loader = loader.getParent();
}
return parent;
}
private String findMain(URL[] urls) {
for (URL url : urls) {
try {
File file = ResourceUtils.getFile(url);
if (file.exists()) {
Archive archive = file.getName().endsWith(".jar")
? new JarFileArchive(file) : new ExplodedArchive(file);
String main = new ComputeLauncher(archive).getMainClass();
if (main != null) {
return main;
}
}
}
catch (Exception e) {
// ignore
}
}
return null;
}
private URL[] expand(URL[] urls) {
List<URL> result = new ArrayList<>();
for (URL url : urls) {
result.addAll(expand(url));
}
return result.toArray(new URL[0]);
}
private List<URL> expand(URL url) {
if (!"file".equals(url.getProtocol())) {
return Collections.singletonList(url);
}
try {
File file = new File(url.toURI());
if (file.exists()) {
Archive archive;
if (!url.toString().endsWith(".jar")) {
if (!new File(file, "BOOT-INF").exists()) {
return Collections.singletonList(url);
}
archive = new ExplodedArchive(file);
}
else {
archive = new JarFileArchive(file);
}
return Arrays
.asList(new ComputeLauncher(archive).getClassLoaderUrls());
}
return Collections.singletonList(url);
}
catch (Exception e) {
throw new IllegalStateException("Cannot create class loader for " + url,
e);
}
}
public void run(String main) {
if (main == null) {
main = this.defaultMain;
}
if (main == null) {
return;
}
if (ClassUtils.isPresent(SpringApplication.class.getName(),
FunctionCreatorConfiguration.this.functionClassLoader)) {
logger.info("SpringApplication available. Bootstrapping: " + main);
ClassLoader contextClassLoader = ClassUtils
.overrideThreadContextClassLoader(
FunctionCreatorConfiguration.this.functionClassLoader);
try {
ApplicationRunner runner = new ApplicationRunner(
FunctionCreatorConfiguration.this.functionClassLoader, main);
// TODO: make the runtime properties configurable
runner.run("--spring.main.webEnvironment=false",
"--spring.cloud.stream.enabled=false",
"--spring.main.bannerMode=OFF",
"--spring.main.webApplicationType=none",
"--function.deployer.enabled=false");
this.runner = runner;
}
finally {
ClassUtils.overrideThreadContextClassLoader(contextClassLoader);
}
}
else {
throw new IllegalStateException(
"SpringApplication not available and main class requested: "
+ main);
}
}
public String[] getFunctionNames() {
Set<String> list = new LinkedHashSet<>();
ClassLoader contextClassLoader = ClassUtils.overrideThreadContextClassLoader(
FunctionCreatorConfiguration.this.functionClassLoader);
try {
if (this.runner.containsBean(FunctionCatalog.class.getName())) {
Object catalog = this.runner.getBean(FunctionCatalog.class.getName());
@SuppressWarnings("unchecked")
Set<String> functions = (Set<String>) this.runner
.evaluate("getNames(#type)", catalog, "type", Function.class);
list.addAll(functions);
@SuppressWarnings("unchecked")
Set<String> consumers = (Set<String>) this.runner
.evaluate("getNames(#type)", catalog, "type", Consumer.class);
list.addAll(consumers);
@SuppressWarnings("unchecked")
Set<String> suppliers = (Set<String>) this.runner
.evaluate("getNames(#type)", catalog, "type", Supplier.class);
list.addAll(suppliers);
}
if (list.isEmpty()) {
list.addAll(this.runner.getBeanNames(Function.class.getName()));
list.addAll(this.runner.getBeanNames(Consumer.class.getName()));
list.addAll(this.runner.getBeanNames(Supplier.class.getName()));
}
return list.toArray(new String[0]);
}
finally {
ClassUtils.overrideThreadContextClassLoader(contextClassLoader);
}
}
public Object create(String type) {
ClassLoader contextClassLoader = ClassUtils.overrideThreadContextClassLoader(
FunctionCreatorConfiguration.this.functionClassLoader);
AutowireCapableBeanFactory factory = FunctionCreatorConfiguration.this.context
.getAutowireCapableBeanFactory();
try {
Object result = null;
if (this.runner != null) {
result = this.runner.getBean(type);
if (result == null) {
if (this.runner.containsBean(FunctionCatalog.class.getName())) {
Object catalog = this.runner
.getBean(FunctionCatalog.class.getName());
result = this.runner.evaluate("lookup(#function).getTarget()",
catalog, "function", type);
if (result != null) {
logger.info("Located registration: " + type + " of type "
+ result.getClass());
}
}
}
else {
logger.info("Located bean: " + type + " of type "
+ result.getClass());
if (result.getClass().getName()
.equals(FunctionRegistration.class.getName())) {
result = this.runner.evaluate("getTarget()", result);
}
}
if (result != null) {
if (result.getClass().getName()
.equals(FluxFunction.class.getName())) {
result = this.runner.evaluate("getTarget()", result);
}
}
}
if (result == null) {
logger.info("No bean found. Instantiating: " + type);
if (ClassUtils.isPresent(type,
FunctionCreatorConfiguration.this.functionClassLoader)) {
result = factory.createBean(ClassUtils.resolveClassName(type,
FunctionCreatorConfiguration.this.functionClassLoader));
}
}
if (result != null) {
return result;
}
throw new IllegalStateException("Cannot create bean for: " + type);
}
finally {
ClassUtils.overrideThreadContextClassLoader(contextClassLoader);
}
}
private FunctionType createFunctionType(Object functionCatalog) {
FunctionType functionType = null;
try {
@SuppressWarnings("unchecked")
String name = ((Set<String>) this.runner.evaluate("getNames(#type)", functionCatalog, "type", null))
.stream().findFirst().orElse(null);
if (name != null) {
Object ft = this.runner.evaluate("getFunctionType(#name)", functionCatalog, "name", name);
Constructor<FunctionType> ftConstructor = FunctionType.class.getDeclaredConstructor(Object.class);
ftConstructor.setAccessible(true);
functionType = ftConstructor.newInstance(ft);
}
}
catch (Exception e) {
throw new IllegalStateException("Failed to extract and map FunctionType", e);
}
return functionType;
}
public void register(Object bean) {
if (bean == null) {
return;
}
FunctionRegistration<Object> registration = new FunctionRegistration<Object>(
bean,
FunctionProperties.functionName(this.counter.getAndIncrement()));
if (this.runner != null) {
if (this.runner.containsBean(FunctionInspector.class.getName())) {
Object functionCatalog = this.runner
.getBean(FunctionInspector.class.getName());
FunctionType type = this.createFunctionType(functionCatalog);
if (type == null) {
Class<?> input = (Class<?>) this.runner.evaluate(
"getInputType(#function)", functionCatalog, "function", bean);
type = FunctionType.from(input);
Class<?> output = findType("getOutputType", functionCatalog, bean);
type = type.to(output);
if (((Boolean) this.runner.evaluate("isMessage(#function)", functionCatalog,
"function", bean))) {
type = type.message();
}
Class<?> inputWrapper = findType("getInputWrapper", functionCatalog, bean);
if (FunctionType.isWrapper(inputWrapper)) {
type = type.wrap(inputWrapper);
}
Class<?> outputWrapper = findType("getOutputWrapper", functionCatalog,
bean);
if (FunctionType.isWrapper(outputWrapper)) {
type = type.wrap(outputWrapper);
}
}
registration.type(this.createFunctionType(functionCatalog));
}
}
else {
registration.type(FunctionType.of(bean.getClass()).getType());
}
registration.target(bean);
if (registration.getType() == null) {
registration.type(FunctionType.of(bean.getClass()).getType());
}
FunctionCreatorConfiguration.this.registry.register(registration);
}
private Class<?> findType(String method, Object inspector, Object bean) {
return (Class<?>) this.runner.evaluate(method + "(#function)", inspector,
"function", bean);
}
public void close() {
if (this.runner != null) {
this.runner.close();
}
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.resource.maven.MavenResource;
import org.springframework.cloud.deployer.resource.maven.MavenResourceLoader;
import org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ResourceLoader;
/**
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnProperty(prefix = "function.deployer", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties
@Import(FunctionCreatorConfiguration.class)
public class FunctionDeployerConfiguration {
@Bean
@ConfigurationProperties("maven")
public MavenProperties mavenProperties() {
return new MavenProperties();
}
@Bean
@ConfigurationProperties("function")
public FunctionProperties functionProperties() {
return new FunctionProperties();
}
@Bean
@ConditionalOnMissingBean(DelegatingResourceLoader.class)
public DelegatingResourceLoader delegatingResourceLoader(
MavenProperties mavenProperties) {
Map<String, ResourceLoader> loaders = new HashMap<>();
loaders.put(MavenResource.URI_SCHEME, new MavenResourceLoader(mavenProperties));
return new DelegatingResourceLoader(loaders);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.util.StringUtils;
/**
* Configuration properties for deciding how to locate the functional class to execute.
*
* @author Eric Bottard
*/
public class FunctionProperties {
/**
* Location(s) of jar archives containing the supplier/function/consumer class to run.
*/
private String[] location = new String[0];
/**
* The bean name or fully qualified class name of the supplier/function/consumer to
* run.
*/
private String[] bean = new String[0];
/**
* Optional main class from which to build a Spring application context.
*/
private String main;
public static String functionName(String name) {
if (!name.contains(",")) {
return "function0";
}
List<String> names = new ArrayList<>();
for (int i = 0; i <= StringUtils.countOccurrencesOf(name, ","); i++) {
names.add("function" + i);
}
return StringUtils.collectionToDelimitedString(names, "|");
}
public static String functionName(int value) {
return "function" + value;
}
public String getName() {
return functionName(StringUtils.arrayToDelimitedString(this.bean, ","));
}
public String[] getBean() {
return this.bean;
}
public void setBean(String[] bean) {
this.bean = bean;
}
public String[] getLocation() {
return this.location;
}
public void setLocation(String[] location) {
this.location = location;
}
public String getMain() {
return this.main;
}
public void setMain(String main) {
this.main = main;
}
@PostConstruct
public void init() {
if (this.location.length == 0) {
throw new IllegalStateException(
"No archive location provided, please configure function.location as a jar or directory.");
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.Collections;
import java.util.Set;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionRegistry;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*
*/
public class SingleEntryFunctionRegistry implements FunctionRegistry {
private final FunctionRegistry delegate;
private final String name;
public SingleEntryFunctionRegistry(FunctionRegistry delegate, String name) {
this.delegate = delegate;
this.name = name;
}
@Override
public <T> T lookup(Class<?> type, String name) {
if (StringUtils.isEmpty(name)) {
if (this.delegate.getNames(type).size() == 1) {
return this.delegate.lookup(type,
this.delegate.getNames(type).iterator().next());
}
name = this.name;
}
return name.equals(this.name) ? this.delegate.lookup(type, name) : null;
}
@Override
public Set<String> getNames(Class<?> type) {
return Collections.singleton(this.name);
}
@Override
public <T> void register(FunctionRegistration<T> registration) {
this.delegate.register(registration);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* A test suite for probing weird ordering problems in the tests.
*
* @author Dave Syer
*/
@RunWith(Suite.class)
@SuiteClasses({ FunctionCreatorConfigurationTests.FunctionCompositionTests.class,
FunctionCreatorConfigurationTests.SingleFunctionTests.class,
FunctionCreatorConfigurationTests.ManualSpringFunctionTests.class,
ContextRunnerTests.class,
SpringFunctionAppConfigurationTests.ProcessorTests.class,
SpringFunctionAppConfigurationTests.SourceTests.class,
FunctionCreatorConfigurationTests.ConsumerCompositionTests.class,
SpringFunctionAppConfigurationTests.CompositeTests.class,
ApplicationRunnerTests.class, SpringFunctionAppConfigurationTests.SinkTests.class,
FunctionCreatorConfigurationTests.SupplierCompositionTests.class })
@Ignore
public class AdhocTestSuite {
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import org.junit.Test;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.test.Doubler;
import org.springframework.cloud.function.test.FunctionApp;
import org.springframework.cloud.function.test.FunctionRegistrar;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*/
public class ApplicationRunnerTests {
@Test
public void startEvaluateAndStop() {
ApplicationRunner runner = new ApplicationRunner(getClass().getClassLoader(),
FunctionApp.class.getName());
runner.run("--spring.main.webEnvironment=false");
assertThat(runner.containsBean(Doubler.class.getName())).isTrue();
assertThat(runner.getBean(Doubler.class.getName())).isNotNull();
runner.close();
}
@Test
public void functional() {
ApplicationRunner runner = new ApplicationRunner(getClass().getClassLoader(),
FunctionRegistrar.class.getName());
runner.run();
assertThat(runner.containsBean(Doubler.class.getName())).isFalse();
assertThat(runner.getBean(FunctionCatalog.class.getName())).isNotNull();
assertThat(runner.getBeanNames(FunctionRegistration.class.getName())).hasSize(2);
runner.close();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.Collections;
import org.junit.Test;
import org.springframework.cloud.function.test.Doubler;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*/
public class ContextRunnerTests {
@Test
public void startEvaluateAndStop() {
ContextRunner runner = new ContextRunner();
runner.run(Doubler.class.getName(), Collections.emptyMap(),
"--spring.main.webEnvironment=false");
assertThat(runner.getContext()).isNotNull();
runner.close();
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { FunctionDeployerConfiguration.class })
@DirtiesContext
public abstract class FunctionCreatorConfigurationTests {
@Autowired
protected FunctionCatalog catalog;
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.location=file:target/test-classes",
"function.bean=org.springframework.cloud.function.test.Doubler" })
public static class SingleFunctionTests extends FunctionCreatorConfigurationTests {
@Test
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = {
"function.location=app:classpath,file:target/test-classes,file:target/test-classes/app",
"function.bean=myDoubler" })
public static class SingleFunctionWithAutoMainTests
extends FunctionCreatorConfigurationTests {
@Test
@Ignore
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = {
"function.location=app:classpath,file:target/test-classes,file:target/test-classes/app",
"function.bean=myDoubler",
"function.main=org.springframework.cloud.function.test.FunctionApp" })
public static class SingleFunctionWithMainTests
extends FunctionCreatorConfigurationTests {
@Test
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = {
"function.location=app:classpath,file:target/test-classes,file:target/test-classes/app",
"function.bean=doubler",
"function.main=org.springframework.cloud.function.test.FunctionRegistrar" })
public static class SingleFunctionWithRegistrarTests
extends FunctionCreatorConfigurationTests {
@Test
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = {
"function.location=app:classpath,file:target/test-classes,file:target/test-classes/app",
"function.bean=frenchizer",
"function.main=org.springframework.cloud.function.test.FunctionRegistrar" })
public static class SingleFunctionWithRegistrarAndRegistrationTests
extends FunctionCreatorConfigurationTests {
@Test
public void testFrenchize() {
Function<Flux<Integer>, Flux<String>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo("deux");
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = {
"function.location=app:classpath,file:target/test-classes,file:target/test-classes/app",
"function.bean=myDoubler",
"function.main=org.springframework.cloud.function.test.FunctionInitializer" })
@Ignore
public static class SingleFunctionWithInitializerTests
extends FunctionCreatorConfigurationTests {
@Test
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.location=app:classpath",
"function.bean=org.springframework.cloud.function.test.SpringDoubler" })
public static class ManualSpringFunctionTests
extends FunctionCreatorConfigurationTests {
@Test
public void testDouble() {
Function<Flux<Integer>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.location=file:target/test-classes",
"function.bean=org.springframework.cloud.function.test.NumberEmitter,"
+ "org.springframework.cloud.function.test.Frenchizer" })
@Ignore
public static class SupplierCompositionTests
extends FunctionCreatorConfigurationTests {
@Test
public void testSupplier() {
Supplier<Integer> function = this.catalog.lookup(Supplier.class, "function0");
assertThat(function).isNull();
}
@Test
public void testFunction() {
Supplier<String> function = this.catalog.lookup(Supplier.class,
"function0|function1");
assertThat(function.get()).isEqualTo("un");
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.location=file:target/test-classes",
"function.bean=org.springframework.cloud.function.test.Doubler,"
+ "org.springframework.cloud.function.test.Frenchizer" })
public static class FunctionCompositionTests
extends FunctionCreatorConfigurationTests {
@Test
public void testFunction() {
Function<Flux<Integer>, Flux<String>> function = this.catalog
.lookup(Function.class, "function0|function1");
assertThat(function.apply(Flux.just(2)).blockFirst()).isEqualTo("quatre");
}
@Test
public void testThen() {
Function<Integer, String> function = this.catalog.lookup(Function.class,
"function1");
assertThat(function).isNull();
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.location=file:target/test-classes",
"function.bean=org.springframework.cloud.function.test.Frenchizer,"
+ "org.springframework.cloud.function.test.Printer" })
public static class ConsumerCompositionTests
extends FunctionCreatorConfigurationTests {
@Rule
public OutputCapture capture = new OutputCapture();
@Test
@Ignore
public void testConsumer() {
Function<Flux<Integer>, Mono<Void>> function = this.catalog
.lookup(Function.class, "function0|function1");
function.apply(Flux.just(2)).block();
this.capture.expect(containsString("Seen deux"));
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.function.Function;
import org.junit.Test;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.catalog.InMemoryFunctionCatalog;
import org.springframework.cloud.function.core.FluxFunction;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
public class SingleEntryFunctionRegistryTests {
private InMemoryFunctionCatalog delegate = new InMemoryFunctionCatalog();
@Test
public void named() {
this.delegate.register(new FunctionRegistration<Foos>(new Foos(), "foo"));
SingleEntryFunctionRegistry registry = new SingleEntryFunctionRegistry(
this.delegate, "foo");
assertThat(((FluxFunction<?, ?>) registry.lookup("")).getTarget()).isInstanceOf(Foos.class);
}
@Test
public void other() {
this.delegate.register(new FunctionRegistration<Foos>(new Foos(), "foo"));
SingleEntryFunctionRegistry registry = new SingleEntryFunctionRegistry(
this.delegate, "foo");
assertThat(((FluxFunction<?, ?>) registry.lookup("")).getTarget()).isInstanceOf(Foos.class);
}
@Test
public void empty() {
this.delegate.register(new FunctionRegistration<Foos>(new Foos(), ""));
SingleEntryFunctionRegistry registry = new SingleEntryFunctionRegistry(
this.delegate, "");
assertThat(((FluxFunction<?, ?>) registry.lookup("")).getTarget()).isInstanceOf(Foos.class);
}
@Test
public void anonymous() {
this.delegate.register(new FunctionRegistration<Foos>(new Foos(), "bar"));
SingleEntryFunctionRegistry registry = new SingleEntryFunctionRegistry(
this.delegate, "foo");
assertThat(((FluxFunction<?, ?>) registry.lookup("")).getTarget()).isInstanceOf(Foos.class);
}
class Foos implements Function<String, String> {
@Override
public String apply(String t) {
return t.toUpperCase();
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FunctionDeployerConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@TestPropertySource(properties = {
"function.location=file:target/it/support/target/function-sample-1.0.0.M1-exec.jar" })
public abstract class SpringFunctionAppConfigurationTests {
@Autowired
protected FunctionCatalog catalog;
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myEmitter",
"function.main=com.example.functions.FunctionApp" })
public static class SourceTests extends SpringFunctionAppConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Supplier<String> function = this.catalog.lookup(Supplier.class,
"function0");
assertThat(function.get()).isEqualTo("one");
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myEmitter,myCounter" })
public static class CompositeTests extends SpringFunctionAppConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Supplier<Integer> function = this.catalog.lookup(Supplier.class,
"function0|function1");
assertThat(function.get()).isEqualTo(3);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myCounter" })
public static class ProcessorTests extends SpringFunctionAppConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Function<Flux<String>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just("spam")).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myDoubler" })
public static class SinkTests extends SpringFunctionAppConfigurationTests {
@Rule
public OutputCapture capture = new OutputCapture();
@Test
@Ignore
public void test() throws Exception {
// Can't assert side effects.
Function<Flux<Integer>, Mono<Void>> function = this.catalog
.lookup(Function.class, "function0");
function.apply(Flux.just(5)).block();
this.capture.expect(containsString(String.format("10%n")));
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FunctionDeployerConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@TestPropertySource(properties = {
"function.location=file:target/it/support/target/dependency" })
public abstract class SpringFunctionAppExplodedConfigurationTests {
@Autowired
protected FunctionCatalog catalog;
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myEmitter",
"function.main=com.example.functions.FunctionApp" })
public static class SourceTests extends SpringFunctionAppExplodedConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Supplier<String> function = this.catalog.lookup(Supplier.class,
"function0");
assertThat(function.get()).isEqualTo("one");
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myEmitter,myCounter" })
public static class CompositeTests
extends SpringFunctionAppExplodedConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Supplier<Integer> function = this.catalog.lookup(Supplier.class,
"function0|function1");
assertThat(function.get()).isEqualTo(3);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myCounter" })
public static class ProcessorTests
extends SpringFunctionAppExplodedConfigurationTests {
@Test
@Ignore
public void test() throws Exception {
Function<Flux<String>, Flux<Integer>> function = this.catalog
.lookup(Function.class, "function0");
assertThat(function.apply(Flux.just("spam")).blockFirst()).isEqualTo(4);
}
}
@EnableAutoConfiguration
@TestPropertySource(properties = { "function.bean=myDoubler" })
@Ignore // @TestPropertySource is not taken into account nor it is visible
public static class SinkTests extends SpringFunctionAppExplodedConfigurationTests {
@Rule
public OutputCapture capture = new OutputCapture();
@Test
public void test() throws Exception {
// Can't assert side effects.
Function<Flux<Integer>, Mono<Void>> function = this.catalog
.lookup(Function.class, "function0");
function.apply(Flux.just(5)).block();
this.capture.expect(containsString(String.format("10%n")));
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2012-2019 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
*
* https://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.deployer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Function;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableFunctionDeployer
public class SpringFunctionFluxConfigurationTests {
private Object catalog;
private Object inspector;
private ApplicationBootstrap bootstrap;
@Before
public void run() {
if (this.bootstrap == null) {
this.bootstrap = new ApplicationBootstrap();
this.bootstrap.run(SpringFunctionFluxConfigurationTests.class,
"--function.location=file:target/it/flux/target/dependency",
"--function.bean=foos",
"--function.main=com.example.functions.FunctionApp");
this.catalog = this.bootstrap.getRunner()
.getBean(FunctionCatalog.class.getName());
this.inspector = this.bootstrap.getRunner()
.getBean(FunctionInspector.class.getName());
}
}
@After
public void close() {
if (this.bootstrap != null) {
this.bootstrap.close();
}
}
@Test
@Ignore
public void test() throws Exception {
@SuppressWarnings("unchecked")
Function<Object, Object> function = (Function<Object, Object>) this.bootstrap
.getRunner()
.evaluate("lookup(T(java.util.function.Function), 'function0')",
this.catalog);
assertThat(function).isNotNull();
Class<?> inputType = (Class<?>) this.bootstrap.getRunner().evaluate(
"getInputType(#function)", this.inspector, "function", function);
assertThat(inputType.getName()).isEqualTo("com.example.functions.Foo");
Object foo = create(inputType);
Class<?> outputType = (Class<?>) this.bootstrap.getRunner().evaluate(
"getOutputType(#function)", this.inspector, "function", function);
assertThat(outputType.getName()).isEqualTo("com.example.functions.Foo");
String value = (String) this.bootstrap.getRunner().evaluate(
"apply(T(reactor.core.publisher.Flux).just(#foo)).blockFirst().getValue()",
function, "foo", foo);
assertThat(value).isEqualTo("FOO");
}
private Object create(Class<?> inputType) throws InstantiationException,
IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Constructor<?> constructor = inputType.getConstructor(String.class);
constructor.setAccessible(true);
return constructor.newInstance("foo");
}
}
class Foo {
private String value;
Foo() {
}
Foo(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Foo [value=" + this.value + "]";
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.util.function.Function;
public class Doubler implements Function<Integer, Integer> {
@Override
public Integer apply(Integer integer) {
return 2 * integer;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.util.function.Function;
import javax.annotation.PostConstruct;
public class Frenchizer implements Function<Integer, String> {
private String[] numbers;
@PostConstruct
public void init() {
this.numbers = new String[4];
this.numbers[0] = "un";
this.numbers[1] = "deux";
this.numbers[2] = "trois";
this.numbers[3] = "quatre";
}
@Override
public String apply(Integer integer) {
if (integer < this.numbers.length + 1) {
return this.numbers[integer - 1];
}
throw new RuntimeException();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
/**
* @author Dave Syer
*/
@SpringBootConfiguration
public class FunctionApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(FunctionApp.class, args);
}
@Bean
public Doubler myDoubler() {
return new Doubler();
}
@Bean
public Frenchizer myFrenchizer() {
return new Frenchizer();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericApplicationContext;
/**
* @author Dave Syer
*/
public class FunctionInitializer
implements ApplicationContextInitializer<GenericApplicationContext> {
public static void main(String[] args) throws Exception {
SpringApplication application = new FunctionalSpringApplication(
FunctionInitializer.class);
application.run(args);
}
@Bean
public Doubler myDoubler() {
return new Doubler();
}
@Bean
public Frenchizer myFrenchizer() {
return new Frenchizer();
}
@Override
public void initialize(GenericApplicationContext context) {
// TODO: support for FunctionRegistration
context.registerBean("myDoubler", Doubler.class, () -> myDoubler());
context.registerBean("myFrenchizer", Frenchizer.class, () -> myFrenchizer());
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericApplicationContext;
/**
* @author Dave Syer
*/
public class FunctionRegistrar
implements ApplicationContextInitializer<GenericApplicationContext> {
public static void main(String[] args) throws Exception {
SpringApplication application = new FunctionalSpringApplication(
FunctionRegistrar.class);
application.run(args);
}
@Bean
public Doubler myDoubler() {
return new Doubler();
}
@Bean
public Frenchizer myFrenchizer() {
return new Frenchizer();
}
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean("theDoubler", FunctionRegistration.class,
() -> new FunctionRegistration<>(myDoubler(), "doubler")
.type(FunctionType.of((Doubler.class))));
context.registerBean("frenchizer", FunctionRegistration.class, () -> {
Frenchizer function = myFrenchizer();
function.init();
return new FunctionRegistration<>(function, "theFrenchizer")
.type(FunctionType.of((Frenchizer.class)));
});
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.util.function.Supplier;
public class NumberEmitter implements Supplier<Integer> {
@Override
public Integer get() {
return 1;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.util.function.Consumer;
public class Printer implements Consumer<Object> {
@Override
public void accept(Object o) {
System.err.println("Seen " + o);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2019 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
*
* https://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.test;
import java.util.function.Function;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
public class SpringDoubler implements Function<Integer, Integer> {
@Autowired
private ConfigurableApplicationContext context;
@PostConstruct
public void init() {
if (this.context == null) {
this.context = new SpringApplicationBuilder(FunctionApp.class)
.bannerMode(Mode.OFF).registerShutdownHook(false)
.web(WebApplicationType.NONE).run();
}
}
@PreDestroy
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Override
public Integer apply(Integer integer) {
return 2 * integer;
}
}

View File

@@ -0,0 +1,14 @@
Manifest-Version: 1.0
Implementation-Title: function-file-sample
Implementation-Version: 1.0.0.M1
Archiver-Version: Plexus Archiver
Built-By: dsyer
Implementation-Vendor-Id: com.example
Spring-Boot-Version: 1.5.12.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: org.springframework.cloud.function.test.FunctionApp
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_131
Implementation-URL: https://projects.spring.io/spring-boot/function-sam
ple/