initial commit
This commit is contained in:
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/application.yml
|
||||
/application.properties
|
||||
asciidoctor.css
|
||||
*~
|
||||
.#*
|
||||
*#
|
||||
target/
|
||||
build/
|
||||
bin/
|
||||
_site/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
.springBeans
|
||||
.DS_Store
|
||||
*.sw*
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
.factorypath
|
||||
coverage-error.log
|
||||
.apt_generated
|
||||
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Example:
|
||||
|
||||
```
|
||||
java -jar spring-cloud-function-stream-1.0.0.BUILD-SNAPSHOT.jar --server.port=8081 --spring.cloud.stream.bindings.input.destination=words --spring.cloud.stream.bindings.output.destination=uppercaseWords --function.name=uppercase --function.code="f -> f.map(s -> s.toString().toUpperCase())"
|
||||
```
|
||||
|
||||
(more docs soon)
|
||||
108
pom.xml
Normal file
108
pom.xml
Normal file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-function-parent</artifactId>
|
||||
<name>Spring Cloud Function Parent</name>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<reactor.version>3.0.0.RELEASE</reactor.version>
|
||||
<spring-cloud-stream.version>1.1.0.BUILD-SNAPSHOT</spring-cloud-stream.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>${reactor.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-function-compiler</module>
|
||||
<module>spring-cloud-function-core</module>
|
||||
<module>spring-cloud-function-stream</module>
|
||||
<!-- module>spring-cloud-function-web</module -->
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<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>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
45
spring-cloud-function-compiler/pom.xml
Normal file
45
spring-cloud-function-compiler/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-function-compiler</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Function Compiler</name>
|
||||
<description>Spring Cloud Function Compiler</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jdt.core.compiler</groupId>
|
||||
<artifactId>ecj</artifactId>
|
||||
<version>4.4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.function.compiler.java.CompilationFailedException;
|
||||
import org.springframework.cloud.function.compiler.java.CompilationMessage;
|
||||
import org.springframework.cloud.function.compiler.java.CompilationResult;
|
||||
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class FunctionCompiler {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(FunctionCompiler.class);
|
||||
|
||||
// Newlines in the property are escaped
|
||||
private static final String NEWLINE_ESCAPE = Matcher.quoteReplacement("\\n");
|
||||
|
||||
// Individual double-quote characters are represented by two double quotes in the DSL
|
||||
private static final String DOUBLE_DOUBLE_QUOTE = Matcher.quoteReplacement("\"\"");
|
||||
|
||||
private final static String PACKAGE = "org.springframework.cloud.function.compiler";
|
||||
|
||||
private final static String MAIN_COMPILED_CLASS_NAME = PACKAGE + ".GeneratedFunctionFactory";
|
||||
|
||||
/**
|
||||
* The user supplied code snippet is inserted into the template and then the result is compiled
|
||||
*/
|
||||
private static String SOURCE_CODE_TEMPLATE =
|
||||
"package " + PACKAGE + ";\n" +
|
||||
"import java.util.*;\n" + // Helpful to include this
|
||||
"import java.util.function.*;\n" +
|
||||
"import reactor.core.publisher.Flux;\n" +
|
||||
"public class GeneratedFunctionFactory implements FunctionFactory {\n" +
|
||||
" public Function<Flux<Object>, Flux<Object>> getFunction() {\n" +
|
||||
" %s\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
|
||||
private final RuntimeJavaCompiler compiler = new RuntimeJavaCompiler();
|
||||
|
||||
/**
|
||||
* Produce a Function instance by:<ul>
|
||||
* <li>Decoding the code String to process any newlines/double-double-quotes
|
||||
* <li>Insert the code into the source code template for a class
|
||||
* <li>Compiling the class using the JDK provided Java Compiler
|
||||
* <li>Loading the compiled class
|
||||
* <li>Invoking a well known method on the factory class to produce a Function instance
|
||||
* <li>Returning that instance.
|
||||
* </ul>
|
||||
*
|
||||
* @return a Function instance
|
||||
*/
|
||||
public <T, R> Function<T, R> compile(String code) {
|
||||
logger.info("Initial code property value :'{}'", code);
|
||||
code = decode(code);
|
||||
if (code.startsWith("\"") && code.endsWith("\"")) {
|
||||
code = code.substring(1,code.length()-1);
|
||||
}
|
||||
if (!code.startsWith("return ") && !code.endsWith(";")) {
|
||||
code = "return " + code + ";";
|
||||
}
|
||||
logger.info("Processed code property value :\n{}\n", code);
|
||||
CompilationResult compilationResult = buildAndCompileSourceCode(code);
|
||||
if (compilationResult.wasSuccessful()) {
|
||||
List<Class<?>> clazzes = compilationResult.getCompiledClasses();
|
||||
logger.info("Compilation resulted in #{} classes", clazzes.size());
|
||||
for (Class<?> clazz: clazzes) {
|
||||
if (clazz.getName().equals(MAIN_COMPILED_CLASS_NAME)) {
|
||||
try {
|
||||
FunctionFactory functionFactory = (FunctionFactory) clazz.newInstance();
|
||||
return functionFactory.getFunction();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Unexpected problem during retrieval of Function from compiled class", e);
|
||||
}
|
||||
}
|
||||
System.out.println(clazz.getName());
|
||||
}
|
||||
logger.error("Failed to find the expected compiled class");
|
||||
}
|
||||
List<CompilationMessage> compilationMessages = compilationResult.getCompilationMessages();
|
||||
throw new CompilationFailedException(compilationMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the source for and then compile and load a class that embodies
|
||||
* the supplied methodBody. The methodBody is inserted into a class template that
|
||||
* returns a <tt>Function<Flux<Object>,Flux<Object>></tt>.
|
||||
* This method can return more than one class if the method body includes local class
|
||||
* declarations. An example methodBody would be <tt>return input -> input.buffer(5).map(list->list.get(0));</tt>.
|
||||
*
|
||||
* @param methodBody the source code for a method that should return a
|
||||
* <tt>Function<Flux<Object>,Flux<Object>></tt>
|
||||
* @return the list of Classes produced by compiling and then loading the snippet of code
|
||||
*/
|
||||
private CompilationResult buildAndCompileSourceCode(String methodBody) {
|
||||
String sourceCode = makeSourceClassDefinition(methodBody);
|
||||
return compiler.compile(MAIN_COMPILED_CLASS_NAME, sourceCode);
|
||||
}
|
||||
|
||||
private static String decode(String input) {
|
||||
return input.replaceAll(NEWLINE_ESCAPE, "\n").replaceAll(DOUBLE_DOUBLE_QUOTE, "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a full source code definition for a class by applying the specified method body
|
||||
* to the Reactive template.
|
||||
*
|
||||
* @param methodBody the code to insert into the Reactive source class template
|
||||
* @return a complete Java Class definition
|
||||
*/
|
||||
public static String makeSourceClassDefinition(String methodBody) {
|
||||
return String.format(SOURCE_CODE_TEMPLATE, methodBody);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public interface FunctionFactory {
|
||||
|
||||
<T, R> Function<T, R> getFunction();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* Common superclass for iterables that need to handle closing when finished
|
||||
* with and that need to handle possible constraints on the values that
|
||||
* are iterated over.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public abstract class CloseableFilterableJavaFileObjectIterable implements Iterable<JavaFileObject> {
|
||||
|
||||
// private final static Logger logger = LoggerFactory.getLogger(CloseableFilterableJavaFileObjectIterable.class);
|
||||
|
||||
private final static boolean BOOT_PACKAGING_AWARE = true;
|
||||
private final static String BOOT_PACKAGING_PREFIX_FOR_CLASSES = "BOOT-INF/classes/";
|
||||
|
||||
// If set specifies the package the iterator consumer is interested in. Only
|
||||
// return results in this package.
|
||||
private String packageNameFilter;
|
||||
|
||||
// Indicates whether the consumer of the iterator wants to see classes
|
||||
// that are in subpackages of those matching the filter.
|
||||
private boolean includeSubpackages;
|
||||
|
||||
public CloseableFilterableJavaFileObjectIterable(String packageNameFilter, boolean includeSubpackages) {
|
||||
if (packageNameFilter!=null && packageNameFilter.contains(File.separator)) {
|
||||
throw new IllegalArgumentException("Package name filters should use dots to separate components: "+packageNameFilter);
|
||||
}
|
||||
this.packageNameFilter = packageNameFilter==null?null:packageNameFilter.replace('.', File.separatorChar) + "/";
|
||||
this.includeSubpackages = includeSubpackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses to check values against any specified constraints.
|
||||
*
|
||||
* @param name the name to check against the criteria
|
||||
* @return true if the name is a valid iterator result based on the specified criteria
|
||||
*/
|
||||
protected boolean accept(String name) {
|
||||
// logger.debug("checking {} against constraints packageNameFilter={} includeSubpackages={}",name,packageNameFilter,includeSubpackages);
|
||||
if (!name.endsWith(".class")) {
|
||||
return false;
|
||||
}
|
||||
if (packageNameFilter == null) {
|
||||
return true;
|
||||
}
|
||||
boolean accept;
|
||||
if (includeSubpackages == true) {
|
||||
accept = name.startsWith(packageNameFilter);
|
||||
if (!accept && BOOT_PACKAGING_AWARE) {
|
||||
accept = name.startsWith(BOOT_PACKAGING_PREFIX_FOR_CLASSES) &&
|
||||
name.indexOf(packageNameFilter)==BOOT_PACKAGING_PREFIX_FOR_CLASSES.length();
|
||||
}
|
||||
} else {
|
||||
accept = name.startsWith(packageNameFilter) && name.indexOf("/",packageNameFilter.length())==-1;
|
||||
if (!accept && BOOT_PACKAGING_AWARE) {
|
||||
accept = name.startsWith(BOOT_PACKAGING_PREFIX_FOR_CLASSES) &&
|
||||
name.indexOf(packageNameFilter)==BOOT_PACKAGING_PREFIX_FOR_CLASSES.length() &&
|
||||
name.indexOf("/",BOOT_PACKAGING_PREFIX_FOR_CLASSES.length()+packageNameFilter.length())==-1;
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
}
|
||||
|
||||
abstract void close();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class CompilationFailedException extends RuntimeException {
|
||||
|
||||
public CompilationFailedException(List<CompilationMessage> messages) {
|
||||
super(consolidateMessages(messages));
|
||||
}
|
||||
|
||||
private static String consolidateMessages(List<CompilationMessage> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (CompilationMessage message : messages) {
|
||||
sb.append(message.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
/**
|
||||
* Encapsulate information produced during compilation. A message may be an error
|
||||
* or something less serious (warning/informational). The <tt>toString()</tt> method
|
||||
* will produce a formatted error include source context indicating the precise
|
||||
* location of the problem.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationMessage {
|
||||
|
||||
private Kind kind;
|
||||
private String message;
|
||||
private String sourceCode;
|
||||
private int startPosition;
|
||||
private int endPosition;
|
||||
|
||||
enum Kind {
|
||||
ERROR, OTHER
|
||||
};
|
||||
|
||||
public CompilationMessage(Kind kind, String message, String sourceCode, int startPosition, int endPosition) {
|
||||
this.kind = kind;
|
||||
this.message = message;
|
||||
this.sourceCode = sourceCode;
|
||||
this.startPosition = startPosition;
|
||||
this.endPosition = endPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type of message
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return this.kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message text
|
||||
*/
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the source code for the file associated with the message
|
||||
*/
|
||||
public String getSourceCode() {
|
||||
return this.sourceCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return offset from start of source file where the error begins
|
||||
*/
|
||||
public int getStartPosition() {
|
||||
return this.startPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return offset from start of source file where the error ends
|
||||
*/
|
||||
public int getEndPosition() {
|
||||
return this.endPosition;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("==========\n");
|
||||
if (sourceCode != null) { // Cannot include source context if no source available
|
||||
int[] lineStartEnd = getLineStartEnd(startPosition);
|
||||
s.append(sourceCode.substring(lineStartEnd[0], lineStartEnd[1])).append("\n");
|
||||
int col = lineStartEnd[0];
|
||||
// When inserting the whitespace, ensure tabs in the source line are respected
|
||||
while ((col) < startPosition) {
|
||||
s.append(sourceCode.charAt(col++)=='\t'?"\t":" ");
|
||||
}
|
||||
// Want at least one ^
|
||||
s.append("^");
|
||||
col++;
|
||||
while ((col++) < endPosition) {
|
||||
s.append("^");
|
||||
}
|
||||
s.append("\n");
|
||||
}
|
||||
s.append(kind).append(":").append(message).append("\n");
|
||||
s.append("==========\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given position in the source code this method returns a pair of int
|
||||
* that indicate the start and end of the line within the source code that
|
||||
* contain the position.
|
||||
*
|
||||
* @param searchPos the position of interest in the source code
|
||||
* @return an int array of length 2 containing the start and end positions of the line
|
||||
*/
|
||||
private int[] getLineStartEnd(int searchPos) {
|
||||
int previousPos = -1;
|
||||
int pos = 0;
|
||||
do {
|
||||
pos = sourceCode.indexOf('\n', previousPos + 1);
|
||||
if (searchPos < pos) {
|
||||
return new int[] { previousPos + 1, pos };
|
||||
}
|
||||
previousPos = pos;
|
||||
} while (pos != -1);
|
||||
return new int[] { previousPos + 1, sourceCode.length() };
|
||||
}
|
||||
// TODO test coverage for first line/last line situations
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
|
||||
/**
|
||||
* During compilation instances of this class will collect up the output files from the compilation process.
|
||||
* Any kind of file is collected upon but access is only currently provided to retrieve classes produced
|
||||
* during compilation. Annotation processors that run may create other kinds of artifact.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationOutputCollector {
|
||||
|
||||
private List<InMemoryJavaFileObject> outputFiles = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Retrieve compiled classes that have been collected since this collector
|
||||
* was built. Due to annotation processing it is possible other source files
|
||||
* or metadata files may be produced during compilation - those are not included
|
||||
* in the returned list.
|
||||
*
|
||||
* @return list of compiled classes
|
||||
*/
|
||||
public List<CompiledClassDefinition> getCompiledClasses() {
|
||||
List<CompiledClassDefinition> compiledClassDefinitions = new ArrayList<>();
|
||||
for (InMemoryJavaFileObject outputFile : outputFiles) {
|
||||
if (outputFile.getKind() == Kind.CLASS) {
|
||||
CompiledClassDefinition compiledClassDefinition = new CompiledClassDefinition(outputFile.getName(),
|
||||
outputFile.getBytes());
|
||||
compiledClassDefinitions.add(compiledClassDefinition);
|
||||
}
|
||||
}
|
||||
return compiledClassDefinitions;
|
||||
}
|
||||
|
||||
public InMemoryJavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) {
|
||||
InMemoryJavaFileObject jfo = InMemoryJavaFileObject.getJavaFileObject(location, className, kind, sibling);
|
||||
outputFiles.add(jfo);
|
||||
return jfo;
|
||||
}
|
||||
|
||||
public InMemoryJavaFileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) {
|
||||
InMemoryJavaFileObject ojfo = InMemoryJavaFileObject.getFileObject(location, packageName, relativeName, sibling);
|
||||
outputFiles.add(ojfo);
|
||||
return ojfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holder for the results of compilation. If compilation was successful the set
|
||||
* of classes that resulted from compilation will be available. If compilation
|
||||
* was not successful the error messages should provide information about why.
|
||||
* Note that compilation may succeed and yet there will still be informational or
|
||||
* warning messages collected.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationResult {
|
||||
|
||||
private boolean successfulCompilation;
|
||||
|
||||
List<CompilationMessage> compilationMessages = new ArrayList<>();
|
||||
|
||||
List<Class<?>> compiledClasses = new ArrayList<>();
|
||||
|
||||
public CompilationResult(boolean successfulCompilation) {
|
||||
this.successfulCompilation = successfulCompilation;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return successfulCompilation;
|
||||
}
|
||||
|
||||
public List<Class<?>> getCompiledClasses() {
|
||||
return compiledClasses;
|
||||
}
|
||||
|
||||
public List<CompilationMessage> getCompilationMessages() {
|
||||
return Collections.unmodifiableList(compilationMessages);
|
||||
}
|
||||
|
||||
public void recordCompilationMessage(CompilationMessage compilationMessage) {
|
||||
this.compilationMessages.add(compilationMessage);
|
||||
}
|
||||
|
||||
public void setCompiledClasses(List<Class<?>> compiledClasses) {
|
||||
this.compiledClasses = compiledClasses;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("Compilation result: #classes="+compiledClasses.size()+" #messages="+compilationMessages.size()+"\n");
|
||||
s.append("Compiled classes:\n").append(compiledClasses).append("\n");
|
||||
s.append("Compilation messages:\n").append(compilationMessages).append("\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Encapsulates a name with the bytes for its class definition.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompiledClassDefinition {
|
||||
|
||||
private byte[] bytes;
|
||||
private String filename;
|
||||
private String classname;
|
||||
|
||||
public CompiledClassDefinition(String filename, byte[] bytes) {
|
||||
this.filename = filename;
|
||||
this.bytes = bytes;
|
||||
this.classname = filename;
|
||||
if (classname.startsWith(File.separator)) {
|
||||
classname = classname.substring(1);
|
||||
}
|
||||
classname = classname.replace(File.separatorChar, '.').substring(0, classname.length()-6);//strip off .class
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "CompiledClassDefinition(name=" + getName() + ",#bytes=" + getBytes().length + ")";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return this.classname;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* A JavaFileObject that represents a file in a directory.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class DirEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File file;
|
||||
private File basedir;
|
||||
|
||||
public DirEntryJavaFileObject(File basedir, File file) {
|
||||
this.basedir = basedir;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
return file.toURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the file relative to the base directory, for example: a/b/c/D.class
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
String basedirPath = basedir.getPath();
|
||||
String filePath = file.getPath();
|
||||
return filePath.substring(basedirPath.length()+1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("Only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("openReader() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // This object is for read only access to a class
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash + 1).equals(simpleName + ".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.getName().hashCode()*37+basedir.getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DirEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
DirEntryJavaFileObject that = (DirEntryJavaFileObject)obj;
|
||||
return (basedir.getName().equals(that.basedir.getName())) && (file.getName().equals(that.file.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Walks a directory hierarchy from some base directory discovering files.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class DirEnumeration implements Enumeration<File> {
|
||||
|
||||
// private final static Logger logger = LoggerFactory.getLogger(DirEnumeration.class);
|
||||
|
||||
// The starting point
|
||||
private File basedir;
|
||||
|
||||
// Candidates collected so far
|
||||
private List<File> filesToReturn;
|
||||
|
||||
// Places still to explore for candidates
|
||||
private List<File> directoriesToExplore;
|
||||
|
||||
public DirEnumeration(File basedir) {
|
||||
this.basedir = basedir;
|
||||
}
|
||||
|
||||
private void computeValue() {
|
||||
if (filesToReturn == null) { // Indicates we haven't started yet
|
||||
filesToReturn = new ArrayList<>();
|
||||
directoriesToExplore = new ArrayList<>();
|
||||
visitDirectory(basedir);
|
||||
}
|
||||
if (filesToReturn.size() == 0) {
|
||||
while (filesToReturn.size() == 0 && directoriesToExplore.size() != 0) {
|
||||
File nextDir = directoriesToExplore.get(0);
|
||||
directoriesToExplore.remove(0);
|
||||
visitDirectory(nextDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
computeValue();
|
||||
return filesToReturn.size() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File nextElement() {
|
||||
computeValue();
|
||||
if (filesToReturn.size()==0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
File toReturn = filesToReturn.get(0);
|
||||
filesToReturn.remove(0);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private void visitDirectory(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file: files) {
|
||||
if (file.isDirectory()) {
|
||||
directoriesToExplore.add(file);
|
||||
} else {
|
||||
filesToReturn.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
// logger.debug("after visiting {} filesToReturn=#{} dirsToExplore=#{}",dir,filesToReturn.size(), directoriesToExplore.size());
|
||||
}
|
||||
|
||||
public File getDirectory() {
|
||||
return basedir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path of this file to the base directory that the directory enumeration was
|
||||
* started for.
|
||||
* @param file a file discovered returned by this enumeration
|
||||
* @return the relative path of the file (for example: a/b/c/D.class)
|
||||
*/
|
||||
public String getName(File file) {
|
||||
String basedirPath = basedir.getPath();
|
||||
String filePath = file.getPath();
|
||||
if (!filePath.startsWith(basedirPath)) {
|
||||
throw new IllegalStateException("The file '"+filePath+"' is not nested below the base directory '"+basedirPath+"'");
|
||||
}
|
||||
return filePath.substring(basedirPath.length()+1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.apache.commons.collections.IteratorUtils;
|
||||
|
||||
/**
|
||||
* Simple iterable that can be used to return an iterator over no values.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
class EmptyIterable extends CloseableFilterableJavaFileObjectIterable {
|
||||
|
||||
static EmptyIterable instance = new EmptyIterable();
|
||||
|
||||
private EmptyIterable() {
|
||||
super(null,false);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterator<JavaFileObject> iterator() {
|
||||
return IteratorUtils.emptyIterator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A JavaFileObject that represents a source artifact created for compilation or an output
|
||||
* artifact producing during compilation (a .class file or some other thing if an annotation
|
||||
* processor has run). In order to be clear what it is being used for there are static factory
|
||||
* methods that ask for specific types of file.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class InMemoryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(InMemoryJavaFileObject.class);
|
||||
|
||||
private Location location;
|
||||
private String packageName;
|
||||
private String relativeName;
|
||||
private FileObject sibling;
|
||||
private String className;
|
||||
private Kind kind;
|
||||
|
||||
private byte[] content = null;
|
||||
private long lastModifiedTime = 0;
|
||||
private URI uri = null;
|
||||
|
||||
private InMemoryJavaFileObject() {}
|
||||
|
||||
public static InMemoryJavaFileObject getFileObject(Location location, String packageName, String relativeName, FileObject sibling) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.kind = Kind.OTHER;
|
||||
retval.location = location;
|
||||
retval.packageName = packageName;
|
||||
retval.relativeName = relativeName;
|
||||
retval.sibling = sibling;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static InMemoryJavaFileObject getJavaFileObject(Location location, String className, Kind kind, FileObject sibling) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.location = location;
|
||||
retval.className = className;
|
||||
retval.kind = kind;
|
||||
retval.sibling = sibling;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static InMemoryJavaFileObject getSourceJavaFileObject(String className, String content) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.location = StandardLocation.SOURCE_PATH;
|
||||
retval.className = className;
|
||||
retval.kind = Kind.SOURCE;
|
||||
retval.content = content.getBytes();
|
||||
return retval;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "OutputJavaFileObject: Location="+location+",className="+className+",kind="+kind+",relativeName="+relativeName+",sibling="+sibling+",packageName="+packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
// These memory based output files 'pretend' to be relative to the file system root
|
||||
if (uri == null) {
|
||||
String name = null;
|
||||
if (className != null) {
|
||||
name = className.replace('.', '/');
|
||||
} else if (packageName !=null && packageName.length()!=0) {
|
||||
name = packageName.replace('.', '/')+'/'+relativeName;
|
||||
} else {
|
||||
name = relativeName;
|
||||
}
|
||||
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "file:/"+name+kind.extension;
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '" + uriString + "'", e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return toUri().getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
if (content == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
logger.debug("opening input stream for {}",getName());
|
||||
return new ByteArrayInputStream(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
logger.debug("opening output stream for {}",getName());
|
||||
return new ByteArrayOutputStream() {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
content = this.toByteArray();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
return new InputStreamReader(openInputStream(), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
if (kind!=Kind.SOURCE) {
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on file object: " + getName());
|
||||
}
|
||||
// Not yet supporting encodings
|
||||
return (content==null?null:new String(content));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
// Let's not enforce this restriction right now
|
||||
// if (kind == Kind.CLASS) {
|
||||
// throw new UnsupportedOperationException("openWriter() not supported on file object: " + getName());
|
||||
// }
|
||||
return new CharArrayWriter() {
|
||||
@Override
|
||||
public void close() {
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
content = new String(toCharArray()).getBytes(); // Ignoring encoding...
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return lastModifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
String baseName = simpleName + kind.extension;
|
||||
return kind.equals(getKind())
|
||||
&& (baseName.equals(toUri().getPath())
|
||||
|| toUri().getPath().endsWith("/" + baseName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Iterable that will produce an iterator that returns classes found
|
||||
* on a specified classpath that meet specified criteria. For jars it finds, the
|
||||
* iterator will go into nested jars - this handles the situation with a
|
||||
* spring boot uberjar.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class IterableClasspath extends CloseableFilterableJavaFileObjectIterable {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(IterableClasspath.class);
|
||||
|
||||
private static final String BOOT_PACKAGING_PREFIX_FOR_LIBRARIES = "BOOT-INF/lib/";
|
||||
|
||||
private List<File> classpathEntries = new ArrayList<>();
|
||||
|
||||
private List<ZipFile> openArchives = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param classpath a classpath of jars/directories
|
||||
* @param packageNameFilter an optional package name if choosing to filter (e.g. com.example)
|
||||
* @param includeSubpackages if true, include results in subpackages of the specified package filter
|
||||
*/
|
||||
IterableClasspath(String classpath, String packageNameFilter, boolean includeSubpackages) {
|
||||
super(packageNameFilter, includeSubpackages);
|
||||
StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator);
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
String nextEntry = tokenizer.nextToken();
|
||||
File f = new File(nextEntry);
|
||||
if (f.exists()) {
|
||||
classpathEntries.add(f);
|
||||
} else {
|
||||
logger.debug("path element does not exist {}",f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (ZipFile openArchive : openArchives) {
|
||||
try {
|
||||
openArchive.close();
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected error closing archive {}",openArchive,ioe);
|
||||
}
|
||||
}
|
||||
openArchives.clear();
|
||||
}
|
||||
|
||||
public Iterator<JavaFileObject> iterator() {
|
||||
return new ClasspathEntriesIterator();
|
||||
}
|
||||
|
||||
class ClasspathEntriesIterator implements Iterator<JavaFileObject> {
|
||||
private int currentClasspathEntriesIndex = 0;
|
||||
|
||||
// Either a directory or an archive will be open at any one time
|
||||
private File openDirectory = null;
|
||||
private DirEnumeration openDirectoryEnumeration = null;
|
||||
|
||||
private ZipFile openArchive = null;
|
||||
private File openFile = null;
|
||||
private ZipEntry nestedZip = null;
|
||||
private Stack<Enumeration<? extends ZipEntry>> openArchiveEnumeration = null;
|
||||
|
||||
private JavaFileObject nextEntry = null;
|
||||
|
||||
private void findNext() {
|
||||
if (nextEntry == null) {
|
||||
try {
|
||||
while (openArchive!=null || openDirectory!=null || currentClasspathEntriesIndex < classpathEntries.size()) {
|
||||
if (openArchive == null && openDirectory == null) {
|
||||
// Open the next item
|
||||
File nextFile = classpathEntries.get(currentClasspathEntriesIndex);
|
||||
if (nextFile.isDirectory()) {
|
||||
openDirectory = nextFile;
|
||||
openDirectoryEnumeration = new DirEnumeration(nextFile);
|
||||
} else {
|
||||
openFile = nextFile;
|
||||
openArchive = new ZipFile(nextFile);
|
||||
openArchives.add(openArchive);
|
||||
openArchiveEnumeration = new Stack<Enumeration<? extends ZipEntry>>();
|
||||
openArchiveEnumeration.push(openArchive.entries());
|
||||
}
|
||||
currentClasspathEntriesIndex++;
|
||||
}
|
||||
if (openArchiveEnumeration != null) {
|
||||
while (!openArchiveEnumeration.isEmpty()) {
|
||||
while (openArchiveEnumeration.peek().hasMoreElements()) {
|
||||
ZipEntry entry = openArchiveEnumeration.peek().nextElement();
|
||||
String entryName = entry.getName();
|
||||
if (accept(entryName)) {
|
||||
if (nestedZip!=null) {
|
||||
nextEntry = new NestedZipEntryJavaFileObject(openFile, openArchive,nestedZip, entry);
|
||||
} else {
|
||||
nextEntry = new ZipEntryJavaFileObject(openFile, openArchive, entry);
|
||||
}
|
||||
return;
|
||||
} else if (nestedZip == null && entryName.startsWith(BOOT_PACKAGING_PREFIX_FOR_LIBRARIES) && entryName.endsWith(".jar")) {
|
||||
// nested jar in uber jar
|
||||
logger.debug("opening nested archive {}",entry.getName());
|
||||
ZipInputStream zis = new ZipInputStream(openArchive.getInputStream(entry));
|
||||
// nextEntry = new NestedZipEntryJavaFileObject(openArchive.firstElement(),openArchive.peek(),entry);
|
||||
Enumeration<? extends ZipEntry> nestedZipEnumerator = new ZipEnumerator(zis);
|
||||
nestedZip = entry;
|
||||
openArchiveEnumeration.push(nestedZipEnumerator);
|
||||
}
|
||||
}
|
||||
openArchiveEnumeration.pop();
|
||||
if (nestedZip ==null) { openArchive = null; openFile = null; }
|
||||
else nestedZip = null;
|
||||
}
|
||||
openArchiveEnumeration = null;
|
||||
openArchive = null;
|
||||
openFile = null;
|
||||
} else if (openDirectoryEnumeration != null) {
|
||||
while (openDirectoryEnumeration.hasMoreElements()) {
|
||||
File entry = openDirectoryEnumeration.nextElement();
|
||||
String name = openDirectoryEnumeration.getName(entry);
|
||||
if (accept(name)) {
|
||||
nextEntry = new DirEntryJavaFileObject(openDirectoryEnumeration.getDirectory(), entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
openDirectoryEnumeration = null;
|
||||
openDirectory = null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected error whilst processing classpath entries",ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
findNext();
|
||||
return nextEntry != null;
|
||||
}
|
||||
|
||||
public JavaFileObject next() {
|
||||
findNext();
|
||||
if (nextEntry == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
JavaFileObject retval = nextEntry;
|
||||
nextEntry = null;
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ZipEnumerator implements Enumeration<ZipEntry>{
|
||||
|
||||
private ZipInputStream zis;
|
||||
private ZipEntry nextEntry = null;
|
||||
|
||||
public ZipEnumerator(ZipInputStream zis) {
|
||||
this.zis = zis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
try {
|
||||
nextEntry = zis.getNextEntry();
|
||||
} catch (IOException ioe) {
|
||||
nextEntry=null;
|
||||
}
|
||||
return nextEntry!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry nextElement() {
|
||||
ZipEntry retval = nextEntry;
|
||||
nextEntry = null;
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A file manager that serves source code from in memory and ensures output results are kept in memory
|
||||
* rather than being flushed out to disk. The JavaFileManager is also used as a lookup mechanism
|
||||
* for resolving types.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class MemoryBasedJavaFileManager implements JavaFileManager {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MemoryBasedJavaFileManager.class);
|
||||
|
||||
private CompilationOutputCollector outputCollector;
|
||||
|
||||
private List<CloseableFilterableJavaFileObjectIterable> toClose = new ArrayList<>();
|
||||
|
||||
public MemoryBasedJavaFileManager() {
|
||||
outputCollector = new CompilationOutputCollector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isSupportedOption(String option) {
|
||||
logger.debug("isSupportedOption({})",option);
|
||||
return -1; // Not yet supporting options
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader(Location location) {
|
||||
// Do not simply return the context classloader as it may get closed and then
|
||||
// be unusable for loading any further classes
|
||||
logger.debug("getClassLoader({})",location);
|
||||
return null; // Do not currently need to load plugins
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)
|
||||
throws IOException {
|
||||
logger.debug("list({},{},{},{})",location,packageName,kinds,recurse);
|
||||
CloseableFilterableJavaFileObjectIterable resultIterable = null;
|
||||
if (location == StandardLocation.PLATFORM_CLASS_PATH && (kinds==null || kinds.contains(Kind.CLASS))) {
|
||||
String sunBootClassPath = System.getProperty("sun.boot.class.path");
|
||||
logger.debug("Creating iterable for boot class path: {}",sunBootClassPath);
|
||||
resultIterable = new IterableClasspath(sunBootClassPath, packageName, recurse);
|
||||
toClose.add(resultIterable);
|
||||
} else if (location == StandardLocation.CLASS_PATH && (kinds==null || kinds.contains(Kind.CLASS))) {
|
||||
String javaClassPath = System.getProperty("java.class.path");
|
||||
logger.debug("Creating iterable for class path: {}",javaClassPath);
|
||||
resultIterable = new IterableClasspath(javaClassPath, packageName, recurse);
|
||||
toClose.add(resultIterable);
|
||||
} else if (location == StandardLocation.SOURCE_PATH) {
|
||||
// There are no 'extra sources'
|
||||
resultIterable = EmptyIterable.instance;
|
||||
} else {
|
||||
// Nothing to list
|
||||
resultIterable = EmptyIterable.instance;
|
||||
}
|
||||
return resultIterable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLocation(Location location) {
|
||||
logger.debug("hasLocation({})",location);
|
||||
return (location == StandardLocation.SOURCE_PATH ||
|
||||
location == StandardLocation.CLASS_PATH ||
|
||||
location == StandardLocation.PLATFORM_CLASS_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inferBinaryName(Location location, JavaFileObject file) {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
return null;
|
||||
}
|
||||
// Kind of ignoring location here... assuming we want basically the FQ type name
|
||||
// Example value from getName(): javax/validation/bootstrap/GenericBootstrap.class
|
||||
String classname = file.getName().replace('/', '.');
|
||||
return classname.substring(0, classname.lastIndexOf(".class"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameFile(FileObject a, FileObject b) {
|
||||
logger.debug("isSameFile({},{})",a,b);
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOption(String current, Iterator<String> remaining) {
|
||||
logger.debug("handleOption({},{})",current,remaining);
|
||||
return false; // This file manager does not manage any options
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
|
||||
logger.debug("getJavaFileForInput({},{},{})",location,className,kind);
|
||||
throw new IllegalStateException("Not expected to be used in this context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
|
||||
throws IOException {
|
||||
logger.debug("getJavaFileForOutput({},{},{},{})",location,className,kind,sibling);
|
||||
// Example parameters: CLASS_OUTPUT, Foo, CLASS, StringBasedJavaSourceFileObject[string:///a/b/c/Foo.java]
|
||||
return outputCollector.getJavaFileForOutput(location, className, kind, sibling);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
|
||||
logger.debug("getFileForInput({},{},{})",location,packageName,relativeName);
|
||||
throw new IllegalStateException("Not expected to be used in this context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)
|
||||
throws IOException {
|
||||
logger.debug("getFileForOutput({},{},{},{})",location,packageName,relativeName,sibling);
|
||||
// This can be called when the annotation config processor runs
|
||||
// Example parameters: CLASS_OUTPUT, , META-INF/spring-configuration-metadata.json, null
|
||||
return outputCollector.getFileForOutput(location, packageName, relativeName, sibling);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (CloseableFilterableJavaFileObjectIterable closeable: toClose) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<CompiledClassDefinition> getCompiledClasses() {
|
||||
return outputCollector.getCompiledClasses();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* Represents an element inside in zip which is itself inside a zip. These objects are
|
||||
* not initially created with the content of the file they represent,
|
||||
* only enough information to find that content because many will
|
||||
* typically be created but only few will be opened.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class NestedZipEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File outerFile;
|
||||
private ZipFile outerZipFile;
|
||||
private ZipEntry innerZipFile;
|
||||
private ZipEntry innerZipFileEntry;
|
||||
|
||||
private URI uri;
|
||||
|
||||
public NestedZipEntryJavaFileObject(File outerFile, ZipFile outerZipFile, ZipEntry innerZipFile, ZipEntry innerZipFileEntry) {
|
||||
this.outerFile = outerFile;
|
||||
this.outerZipFile = outerZipFile;
|
||||
this.innerZipFile = innerZipFile;
|
||||
this.innerZipFileEntry = innerZipFileEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return innerZipFileEntry.getName(); // Example: a/b/C.class
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
if (uri == null) {
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "zip:"+outerFile.getAbsolutePath()+"!"+innerZipFile.getName()+"!"+innerZipFileEntry.getName();
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '"+uriString+"'",e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
// Find the inner zip file inside the outer zip file, then
|
||||
// find the relevant entry, then return the stream.
|
||||
InputStream innerZipFileInputStream = this.outerZipFile.getInputStream(innerZipFile);
|
||||
ZipInputStream innerZipInputStream = new ZipInputStream(innerZipFileInputStream);
|
||||
ZipEntry nextEntry = innerZipInputStream.getNextEntry();
|
||||
while (nextEntry != null) {
|
||||
if (nextEntry.getName().equals(innerZipFileEntry.getName())) {
|
||||
return innerZipInputStream;
|
||||
}
|
||||
nextEntry = innerZipInputStream.getNextEntry();
|
||||
}
|
||||
throw new IllegalStateException("Unable to locate nested zip entry "+innerZipFileEntry.getName()+" in zip "+innerZipFile.getName()+" inside zip "+outerZipFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return innerZipFileEntry.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
// The filtering before this object was created ensure it is only used for classes
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // Cannot delete entries inside nested zips
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("cannot write to nested zip entry: "+toUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("cannot write to nested zip entry: "+toUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash+1).equals(simpleName+".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null; // nesting level not known
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null; // access level not known
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = outerFile.getName().hashCode();
|
||||
hc = hc * 37 + innerZipFile.getName().hashCode();
|
||||
hc = hc * 37 + innerZipFileEntry.getName().hashCode();
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof NestedZipEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
NestedZipEntryJavaFileObject that = (NestedZipEntryJavaFileObject)obj;
|
||||
return (outerFile.getName().equals(that.outerFile.getName())) &&
|
||||
(innerZipFile.getName().equals(that.innerZipFile.getName())) &&
|
||||
(innerZipFileEntry.getName().equals(that.innerZipFileEntry.getName()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Compile Java source at runtime and load it.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class RuntimeJavaCompiler {
|
||||
|
||||
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RuntimeJavaCompiler.class);
|
||||
|
||||
/**
|
||||
* Compile the named class consisting of the supplied source code. If successful load the class
|
||||
* and return it. Multiple classes may get loaded if the source code included anonymous/inner/local
|
||||
* classes.
|
||||
* @param className the name of the class (dotted form, e.g. com.foo.bar.Goo)
|
||||
* @param classSourceCode the full source code for the class
|
||||
* @return a CompilationResult that encapsulates what happened during compilation (classes/messages produced)
|
||||
*/
|
||||
public CompilationResult compile(String className, String classSourceCode) {
|
||||
logger.info("Compiling source for class {} using compiler {}",className,compiler.getClass().getName());
|
||||
|
||||
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
|
||||
MemoryBasedJavaFileManager fileManager = new MemoryBasedJavaFileManager();
|
||||
// JavaFileObject sourceFile = new StringBasedJavaSourceFileObject(className, classSourceCode);
|
||||
JavaFileObject sourceFile = InMemoryJavaFileObject.getSourceJavaFileObject(className, classSourceCode);
|
||||
// new InMemoryJavaFileObject(StandardLocation.SOURCE_PATH, className, javax.tools.JavaFileObject.Kind.SOURCE, null);
|
||||
// try (Writer w = sourceFile.openWriter()) {
|
||||
// w.write(classSourceCode);
|
||||
// } catch (IOException ioe) {
|
||||
// ioe.printStackTrace();
|
||||
// }
|
||||
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceFile);
|
||||
CompilationTask task = compiler.getTask(null, fileManager , diagnosticCollector, null, null, compilationUnits);
|
||||
|
||||
boolean success = task.call();
|
||||
CompilationResult compilationResult = new CompilationResult(success);
|
||||
|
||||
// If successful there may be no errors but there might be info/warnings
|
||||
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
|
||||
CompilationMessage.Kind kind = (diagnostic.getKind()==Kind.ERROR?CompilationMessage.Kind.ERROR:CompilationMessage.Kind.OTHER);
|
||||
// String sourceCode = ((StringBasedJavaSourceFileObject)diagnostic.getSource()).getSourceCode();
|
||||
String sourceCode =null;
|
||||
try {
|
||||
sourceCode = (String)diagnostic.getSource().getCharContent(true);
|
||||
} catch (IOException ioe) {
|
||||
// Unexpected, but leave sourceCode null to indicate it was not retrievable
|
||||
}
|
||||
int startPosition = (int)diagnostic.getPosition();
|
||||
if (startPosition == Diagnostic.NOPOS) {
|
||||
startPosition = (int)diagnostic.getStartPosition();
|
||||
}
|
||||
CompilationMessage compilationMessage = new CompilationMessage(kind,diagnostic.getMessage(null),sourceCode,startPosition,(int)diagnostic.getEndPosition());
|
||||
compilationResult.recordCompilationMessage(compilationMessage);
|
||||
}
|
||||
if (success) {
|
||||
List<CompiledClassDefinition> ccds = fileManager.getCompiledClasses();
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
try (SimpleClassLoader ccl = new SimpleClassLoader(this.getClass().getClassLoader())) {
|
||||
for (CompiledClassDefinition ccd: ccds) {
|
||||
Class<?> clazz = ccl.defineClass(ccd.getClassName(), ccd.getBytes());
|
||||
classes.add(clazz);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected exception defining classes",ioe);
|
||||
}
|
||||
compilationResult.setCompiledClasses(classes);
|
||||
}
|
||||
return compilationResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* Very simple classloader that can be used to load the compiled types.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class SimpleClassLoader extends URLClassLoader {
|
||||
|
||||
private static final URL[] NO_URLS = new URL[0];
|
||||
|
||||
public SimpleClassLoader(ClassLoader classLoader) {
|
||||
super(NO_URLS, classLoader);
|
||||
}
|
||||
|
||||
public Class<?> defineClass(String name, byte[] bytes) {
|
||||
return super.defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
public class ZipEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File containingFile;
|
||||
private ZipFile zf;
|
||||
private ZipEntry ze;
|
||||
|
||||
private URI uri;
|
||||
|
||||
public ZipEntryJavaFileObject(File containingFile, ZipFile zipFile, ZipEntry entry) {
|
||||
this.containingFile = containingFile;
|
||||
this.zf = zipFile;
|
||||
this.ze = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
if (uri == null) {
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "zip:" + containingFile.getAbsolutePath() + "!" + ze.getName();
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '" + uriString + "'", e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ze.getName(); // a/b/C.class
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return zf.getInputStream(ze);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("openReader() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return ze.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // Cannot delete entries inside zips
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash + 1).equals(simpleName + ".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = containingFile.getName().hashCode();
|
||||
hc = hc * 37 + ze.getName().hashCode();
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ZipEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
ZipEntryJavaFileObject that = (ZipEntryJavaFileObject)obj;
|
||||
return (containingFile.getName().equals(that.containingFile.getName())) &&
|
||||
(ze.getName().equals(that.ze.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
0
spring-cloud-function-core/.jdk8
Normal file
0
spring-cloud-function-core/.jdk8
Normal file
36
spring-cloud-function-core/pom.xml
Normal file
36
spring-cloud-function-core/pom.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-function-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Function Core</name>
|
||||
<description>Spring Cloud Function Core</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-compiler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.gateway;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
|
||||
import org.springframework.cloud.function.invoker.FunctionInvokingRunnable;
|
||||
import org.springframework.cloud.function.registry.FunctionRegistry;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class FunctionGateway {
|
||||
|
||||
private final FunctionRegistry registry;
|
||||
|
||||
private final TaskScheduler scheduler;
|
||||
|
||||
public FunctionGateway(FunctionRegistry registry, TaskScheduler scheduler) {
|
||||
Assert.notNull(registry, "FunctionRegistry must not be null");
|
||||
Assert.notNull(scheduler, "TaskScheduler must not be null");
|
||||
this.registry = registry;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void compose(String name, Function<?, ?>... functions) {
|
||||
Assert.isTrue(functions != null && functions.length > 1, "more than one Function is required");
|
||||
@SuppressWarnings("rawtypes")
|
||||
Function function = functions[0];
|
||||
for (int i = 1; i < functions.length; i++) {
|
||||
function = function.andThen(functions[i]);
|
||||
}
|
||||
this.registry.register(name, function);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void compose(String composedFunctionName, String... functionNames) {
|
||||
Assert.isTrue(functionNames != null && functionNames.length > 1, "more than one Function is required");
|
||||
@SuppressWarnings("rawtypes")
|
||||
Function function = this.registry.lookup(functionNames[0]);
|
||||
for (int i = 1; i < functionNames.length; i++) {
|
||||
function = function.andThen(this.registry.lookup(functionNames[i]));
|
||||
}
|
||||
this.registry.register(composedFunctionName, function);
|
||||
}
|
||||
|
||||
public <T, R> R invoke(String functionName, T request) {
|
||||
Function<T, R> function = this.registry.lookup(functionName);
|
||||
return function.apply(request);
|
||||
}
|
||||
|
||||
public <T, R> void schedule(String functionName, Trigger trigger, Supplier<T> supplier, Consumer<R> consumer) {
|
||||
Function<T, R> function = this.registry.lookup(functionName);
|
||||
this.scheduler.schedule(new FunctionInvokingRunnable(supplier, function, consumer), trigger);
|
||||
}
|
||||
|
||||
public <T, R> void subscribe(Publisher<T> publisher, String functionName, final Consumer<R> consumer) {
|
||||
final Function<T, R> function = this.registry.lookup(functionName);
|
||||
publisher.subscribe(new Subscriber<T>() {
|
||||
|
||||
@Override
|
||||
public void onComplete() {}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {}
|
||||
|
||||
@Override
|
||||
public void onNext(T next) {
|
||||
if (consumer != null) {
|
||||
consumer.accept(function.apply(next));
|
||||
}
|
||||
else {
|
||||
function.apply(next);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription subscription) {
|
||||
subscription.request(Long.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.invoker;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*
|
||||
* @param <T> function parameter type
|
||||
* @param <R> function return type
|
||||
*/
|
||||
public abstract class AbstractFunctionInvoker<T, R> {
|
||||
|
||||
private final Function<T, R> function;
|
||||
|
||||
protected AbstractFunctionInvoker(Function<T, R> function) {
|
||||
Assert.notNull(function, "Function must not be null");
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
protected R doInvoke(T input) {
|
||||
return this.function.apply(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.invoker;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*
|
||||
* @param <T> output of supplier, input to function
|
||||
* @param <R> output of function, input to consumer
|
||||
*/
|
||||
public class FunctionInvokingRunnable<T, R> implements Runnable {
|
||||
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
private final Function<T, R> function;
|
||||
|
||||
private final Consumer<R> consumer;
|
||||
|
||||
public FunctionInvokingRunnable(Supplier<T> supplier, Function<T, R> function, Consumer<R> consumer) {
|
||||
this.supplier = supplier;
|
||||
this.function = function;
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.consumer.accept(this.function.apply(this.supplier.get()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.registry;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public interface FunctionRegistry {
|
||||
|
||||
<T, R> Function<T, R> lookup(String name);
|
||||
|
||||
void register(String name, Function<?, ?> function);
|
||||
|
||||
void register(String name, String function);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.registry;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.cloud.function.compiler.FunctionCompiler;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class FunctionRegistrySupport implements FunctionRegistry {
|
||||
|
||||
private final FunctionCompiler compiler = new FunctionCompiler();
|
||||
|
||||
@Override
|
||||
public void register(String name, String code) {
|
||||
Function<?, ?> function = compiler.compile(code);
|
||||
this.register(name, function);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.registry;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class InMemoryFunctionRegistry extends FunctionRegistrySupport {
|
||||
|
||||
private final ConcurrentHashMap<String, Function<?, ?>> map = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Function<?, ?> lookup(String name) {
|
||||
return this.map.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String name, Function<?, ?> function) {
|
||||
this.map.put(name, function);
|
||||
}
|
||||
}
|
||||
0
spring-cloud-function-stream/.jdk8
Normal file
0
spring-cloud-function-stream/.jdk8
Normal file
50
spring-cloud-function-stream/pom.xml
Normal file
50
spring-cloud-function-stream/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-function-stream</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Function Stream Support</name>
|
||||
<description>Spring Cloud Function Stream Support</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-reactive</artifactId>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.stream;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class StreamApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(StreamApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.stream;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.function.invoker.AbstractFunctionInvoker;
|
||||
import org.springframework.cloud.function.registry.FunctionRegistry;
|
||||
import org.springframework.cloud.function.registry.InMemoryFunctionRegistry;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableConfigurationProperties(StreamConfigurationProperties.class)
|
||||
public class StreamConfiguration {
|
||||
|
||||
@Autowired
|
||||
private StreamConfigurationProperties properties;
|
||||
|
||||
@Bean
|
||||
public FunctionRegistry registry() {
|
||||
FunctionRegistry registry = new InMemoryFunctionRegistry();
|
||||
registry.register(properties.getName(), properties.getCode());
|
||||
return registry;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AbstractFunctionInvoker<?,?> invoker() {
|
||||
return new StreamListeningFunctionInvoker(registry().lookup(properties.getName()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.stream;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "function")
|
||||
public class StreamConfigurationProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
private String code;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.stream;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.cloud.function.invoker.AbstractFunctionInvoker;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class StreamListeningFunctionInvoker extends AbstractFunctionInvoker<Flux<Object>, Flux<Object>> {
|
||||
|
||||
public StreamListeningFunctionInvoker(Function<Flux<Object>, Flux<Object>> function) {
|
||||
super(function);
|
||||
}
|
||||
|
||||
@StreamListener
|
||||
@Output(Processor.OUTPUT)
|
||||
public Flux<Object> handle(@Input(Processor.INPUT) Flux<Object> input) {
|
||||
return this.doInvoke(input);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user