Add a sample app with just beans that are Functions
Make it deployable via its maven coordinates in spring-cloud-function-deployer (it is deployed by default on start up right now, but that's just a demo)
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
|
||||
import org.springframework.boot.Banner.Mode;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.DependencyResolutionContext;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.thin.AetherEngine;
|
||||
import org.springframework.boot.loader.thin.ArchiveUtils;
|
||||
import org.springframework.cloud.deployer.thin.ContextRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
// NOT a @Component (to prevent it from being scanned by the "main" application).
|
||||
public class ApplicationRunner implements CommandLineRunner {
|
||||
|
||||
private static final String DEFAULT_REACTOR_VERSION = "3.0.3.RELEASE";
|
||||
|
||||
private static Log logger = LogFactory.getLog(ApplicationRunner.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ApplicationRunner().start(args);
|
||||
}
|
||||
|
||||
public ConfigurableApplicationContext start(String... args) {
|
||||
return new SpringApplicationBuilder(ApplicationRunner.class).web(false)
|
||||
.contextClass(AnnotationConfigApplicationContext.class)
|
||||
.bannerMode(Mode.OFF).properties("spring.main.applicationContextClass="
|
||||
+ AnnotationConfigApplicationContext.class.getName())
|
||||
.run(args);
|
||||
}
|
||||
|
||||
private Object app;
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
ClassLoader classLoader = createClassLoader();
|
||||
ClassUtils.overrideThreadContextClassLoader(classLoader);
|
||||
Class<?> cls = classLoader.loadClass(ContextRunner.class.getName());
|
||||
this.app = cls.newInstance();
|
||||
runContext(DeployedFunctionApplication.class.getName(),
|
||||
Collections.emptyMap(), args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Cannot deploy", e);
|
||||
}
|
||||
finally {
|
||||
ClassUtils.overrideThreadContextClassLoader(contextLoader);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
closeContext();
|
||||
}
|
||||
|
||||
private void runContext(String mainClass, Map<String, String> properties,
|
||||
String... args) {
|
||||
Method method = ReflectionUtils.findMethod(this.app.getClass(), "run",
|
||||
String.class, Map.class, String[].class);
|
||||
ReflectionUtils.invokeMethod(method, this.app, mainClass, properties, args);
|
||||
}
|
||||
|
||||
private void closeContext() {
|
||||
Method method = ReflectionUtils.findMethod(this.app.getClass(), "close");
|
||||
ReflectionUtils.invokeMethod(method, this.app);
|
||||
}
|
||||
|
||||
private ClassLoader createClassLoader() {
|
||||
ClassLoader base = getClass().getClassLoader();
|
||||
if (!(base instanceof URLClassLoader)) {
|
||||
throw new IllegalStateException("Need a URL class loader, found: " + base);
|
||||
}
|
||||
@SuppressWarnings("resource")
|
||||
URLClassLoader urlClassLoader = (URLClassLoader) base;
|
||||
URL[] urls = urlClassLoader.getURLs();
|
||||
List<URL> child = new ArrayList<>();
|
||||
List<URL> parent = new ArrayList<>();
|
||||
for (URL url : urls) {
|
||||
child.add(url);
|
||||
}
|
||||
String reactor = getReactorCoordinates();
|
||||
DependencyResolutionContext context = new DependencyResolutionContext();
|
||||
AetherEngine engine = AetherEngine.create(
|
||||
RepositoryConfigurationFactory.createDefaultRepositoryConfiguration(),
|
||||
context);
|
||||
try {
|
||||
List<File> resolved = engine.resolve(Arrays
|
||||
.asList(new Dependency(new DefaultArtifact(reactor), "runtime")));
|
||||
for (File archive : resolved) {
|
||||
try {
|
||||
URL url = archive.toURI().toURL();
|
||||
parent.add(url);
|
||||
child.remove(url);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new IllegalStateException("Cannot locate jar for: " + archive);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArtifactResolutionException e) {
|
||||
throw new IllegalStateException("Cannot resolve archive for " + reactor, e);
|
||||
}
|
||||
logger.info("Parent: " + parent);
|
||||
logger.info("Child: " + child);
|
||||
if (!parent.isEmpty()) {
|
||||
base = new URLClassLoader(parent.toArray(new URL[0]), base.getParent());
|
||||
}
|
||||
return new URLClassLoader(child.toArray(new URL[0]), base);
|
||||
}
|
||||
|
||||
private String getReactorCoordinates() {
|
||||
Package pkg = Flux.class.getPackage();
|
||||
String version = null;
|
||||
version = (pkg != null ? pkg.getImplementationVersion()
|
||||
: DEFAULT_REACTOR_VERSION);
|
||||
if (version == null) {
|
||||
Archive archive = ArchiveUtils.getArchive(Flux.class);
|
||||
try {
|
||||
String path = archive.getUrl().toString();
|
||||
if (path.endsWith("!/")) {
|
||||
path = path.substring(0, path.length() - 2);
|
||||
}
|
||||
path = StringUtils.getFilename(path);
|
||||
if (path.startsWith("reactor-core-")) {
|
||||
path = path.substring("reactor-core-".length());
|
||||
}
|
||||
if (path.endsWith(".jar")) {
|
||||
path = path.substring(0, path.length() - ".jar".length());
|
||||
}
|
||||
version = path;
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (version == null) {
|
||||
version = DEFAULT_REACTOR_VERSION;
|
||||
}
|
||||
return "io.projectreactor:reactor-core:" + version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DeployedFunctionApplication {
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class DeployedFunctionController {
|
||||
|
||||
private final FunctionExtractingAppDeployer deployer;
|
||||
|
||||
@Autowired
|
||||
public DeployedFunctionController(FunctionExtractingAppDeployer deployer) {
|
||||
this.deployer = deployer;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/{name}", consumes = MediaType.TEXT_PLAIN_VALUE)
|
||||
public Flux<String> function(@PathVariable String name,
|
||||
@RequestBody Flux<String> body) {
|
||||
Function<Object, Object> function;
|
||||
if (name.contains(",")) {
|
||||
function = deployer.composeFunction(name.split(","));
|
||||
}
|
||||
else {
|
||||
function = deployer.lookupFunction(name);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Flux<String> result = (Flux<String>) function.apply(body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("/{name}")
|
||||
public Flux<String> supplier(@PathVariable String name) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Flux<String> result = (Flux<String>) deployer.lookupSupplier(name).get();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.loader.thin.ArchiveUtils;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDefinition;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class FunctionAdminController implements CommandLineRunner {
|
||||
|
||||
private final FunctionExtractingAppDeployer deployer;
|
||||
|
||||
private Map<String, String> deployed = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, String> names = new LinkedHashMap<>();
|
||||
|
||||
@Autowired
|
||||
public FunctionAdminController(FunctionExtractingAppDeployer deployer) {
|
||||
this.deployer = deployer;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/{name}")
|
||||
public Map<String, Object> push(@PathVariable String name, @RequestParam String path)
|
||||
throws Exception {
|
||||
String id = deploy(name, path);
|
||||
return Collections.singletonMap("id", id);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{name}")
|
||||
public Map<String, Object> undeploy(@PathVariable String name,
|
||||
@RequestParam String path) throws Exception {
|
||||
String id = names.get(name);
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("No such app");
|
||||
}
|
||||
deployer.undeploy(id);
|
||||
names.remove(name);
|
||||
deployed.remove(id);
|
||||
return Collections.singletonMap("id", id);
|
||||
}
|
||||
|
||||
@GetMapping({ "", "/" })
|
||||
public Map<String, Object> deployed() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
for (String name : names.keySet()) {
|
||||
result.put(name, new DeployedArtifact(name, names.get(name),
|
||||
deployed.get(names.get(name))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
deploy("sample", "maven://com.example:function-sample:1.0.0.BUILD-SNAPSHOT");
|
||||
}
|
||||
|
||||
private String deploy(String name, String path, String... args) throws Exception {
|
||||
Resource resource = new FileSystemResource(
|
||||
ArchiveUtils.getArchiveRoot(ArchiveUtils.getArchive(path)));
|
||||
AppDefinition definition = new AppDefinition(resource.getFilename(),
|
||||
Collections.emptyMap());
|
||||
AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,
|
||||
Collections.emptyMap(), Arrays.asList(args));
|
||||
String deployed = deployer.deploy(request);
|
||||
this.deployed.put(deployed, path);
|
||||
this.names.put(deployed, name);
|
||||
return deployed;
|
||||
}
|
||||
}
|
||||
|
||||
class DeployedArtifact {
|
||||
|
||||
private String name;
|
||||
private String id;
|
||||
private String path;
|
||||
|
||||
public DeployedArtifact() {
|
||||
}
|
||||
|
||||
public DeployedArtifact(String name, String id, String path) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
|
||||
import org.springframework.cloud.deployer.thin.ThinJarAppDeployer;
|
||||
import org.springframework.cloud.deployer.thin.ThinJarAppWrapper;
|
||||
import org.springframework.cloud.function.registry.FunctionCatalog;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class FunctionExtractingAppDeployer extends ThinJarAppDeployer
|
||||
implements FunctionCatalog {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(FunctionExtractingAppDeployer.class);
|
||||
|
||||
private final Map<String, Function<?, ?>> functions = new HashMap<>();
|
||||
private final Map<String, Consumer<?>> consumers = new HashMap<>();
|
||||
private final Map<String, Supplier<?>> suppliers = new HashMap<>();
|
||||
|
||||
public FunctionExtractingAppDeployer() {
|
||||
this("thin", "slim");
|
||||
}
|
||||
|
||||
public FunctionExtractingAppDeployer(String name, String... profiles) {
|
||||
super(name, profiles);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Consumer<T> lookupConsumer(String name) {
|
||||
return (Consumer<T>) consumers.get(name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T, R> Function<T, R> lookupFunction(String name) {
|
||||
return (Function<T, R>) functions.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> Function<T, R> composeFunction(String... functionNames) {
|
||||
Function<T, R> function = this.lookupFunction(functionNames[0]);
|
||||
for (int i = 1; i < functionNames.length; i++) {
|
||||
function = function.andThen(this.lookupFunction(functionNames[i]));
|
||||
}
|
||||
return function;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Supplier<T> lookupSupplier(String name) {
|
||||
return (Supplier<T>) suppliers.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deploy(AppDeploymentRequest request) {
|
||||
String id = super.deploy(request);
|
||||
functions.putAll(functions(id));
|
||||
suppliers.putAll(suppliers(id));
|
||||
consumers.putAll(consumers(id));
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undeploy(String id) {
|
||||
super.undeploy(id);
|
||||
for (String name : functions(id).keySet()) {
|
||||
functions.remove(name);
|
||||
}
|
||||
for (String name : suppliers(id).keySet()) {
|
||||
suppliers.remove(name);
|
||||
}
|
||||
for (String name : consumers(id).keySet()) {
|
||||
consumers.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Function<?, ?>> functions(String id) {
|
||||
Map<String, Function<?, ?>> map = new HashMap<>();
|
||||
ThinJarAppWrapper wrapper = getWrapper(id);
|
||||
if (wrapper == null) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ? extends Function<?, ?>> result = (Map<String, ? extends Function<?, ?>>) getBeans(
|
||||
wrapper, Function.class);
|
||||
map.putAll(result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot extract functions", e);
|
||||
}
|
||||
logger.info("Loaded functions: " + map.keySet());
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Consumer<?>> consumers(String id) {
|
||||
Map<String, Consumer<?>> map = new HashMap<>();
|
||||
ThinJarAppWrapper wrapper = getWrapper(id);
|
||||
if (wrapper == null) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ? extends Consumer<?>> result = (Map<String, ? extends Consumer<?>>) getBeans(
|
||||
wrapper, Consumer.class);
|
||||
map.putAll(result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot extract consumers", e);
|
||||
}
|
||||
logger.info("Loaded consumers: " + map.keySet());
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Supplier<?>> suppliers(String id) {
|
||||
Map<String, Supplier<?>> map = new HashMap<>();
|
||||
ThinJarAppWrapper wrapper = getWrapper(id);
|
||||
if (wrapper == null) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ? extends Supplier<?>> result = (Map<String, ? extends Supplier<?>>) getBeans(
|
||||
wrapper, Supplier.class);
|
||||
map.putAll(result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot extract suppliers", e);
|
||||
}
|
||||
logger.info("Loaded suppliers: " + map.keySet());
|
||||
return map;
|
||||
}
|
||||
|
||||
private <T> Map<String, ? extends T> getBeans(ThinJarAppWrapper wrapper,
|
||||
Class<T> type) throws IllegalAccessException, ClassNotFoundException,
|
||||
NoSuchMethodException, InvocationTargetException {
|
||||
Object app = findContext(wrapper);
|
||||
MethodInvoker invoker = new MethodInvoker();
|
||||
invoker.setTargetObject(app);
|
||||
invoker.setTargetMethod("getBeansOfType");
|
||||
invoker.setArguments(new Object[] { type });
|
||||
invoker.prepare();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, T> result = (Map<String, T>) invoker.invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object findContext(ThinJarAppWrapper wrapper) {
|
||||
Object app = wrapper.getApp();
|
||||
Field field = ReflectionUtils.findField(app.getClass(), "context");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
app = ReflectionUtils.getField(field, app);
|
||||
return app;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.function.registry.DefaultFunctionRegistryAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(FunctionExtractingAppDeployer.class)
|
||||
@AutoConfigureBefore(DefaultFunctionRegistryAutoConfiguration.class)
|
||||
public class FunctionExtractingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FunctionExtractingAppDeployer functionCatalog() {
|
||||
return new FunctionExtractingAppDeployer();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.function.deployer.FunctionExtractingAutoConfiguration
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class FunctionExtractingAppDeployerIntegrationTests {
|
||||
|
||||
private static ConfigurableApplicationContext context;
|
||||
private static int port;
|
||||
|
||||
@BeforeClass
|
||||
public static void open() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
context = new ApplicationRunner().start("--server.port=" + port);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(new TestRestTemplate()
|
||||
.getForObject("http://localhost:" + port + "/words", String.class))
|
||||
.isEqualTo("foobar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2016-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.loader.thin.ArchiveUtils;
|
||||
import org.springframework.boot.loader.tools.LogbackInitializer;
|
||||
import org.springframework.cloud.deployer.spi.app.DeploymentState;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDefinition;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class FunctionExtractingAppDeployerTests {
|
||||
|
||||
private static String id;
|
||||
|
||||
static {
|
||||
LogbackInitializer.initialize();
|
||||
}
|
||||
|
||||
private static FunctionExtractingAppDeployer deployer = new FunctionExtractingAppDeployer();
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
if (id == null) {
|
||||
id = deploy("maven://com.example:function-sample:1.0.0.BUILD-SNAPSHOT");
|
||||
// "--debug");
|
||||
}
|
||||
assertThat(deployer.status(id).getState()).isEqualTo(DeploymentState.deployed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deployAndExtractFunctions() throws Exception {
|
||||
// This one can only work if you change the boot classpath to contain reactor-core
|
||||
// and reactive-streams
|
||||
expected.expect(ClassCastException.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Flux<String> result = (Flux<String>) deployer.lookupFunction("uppercase")
|
||||
.apply(Flux.just("foo"));
|
||||
assertThat(result.blockFirst()).isEqualTo("FOO");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deployAndExtractConsumers() throws Exception {
|
||||
assertThat(deployer.lookupConsumer("sink")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deployAndExtractSuppliers() throws Exception {
|
||||
assertThat(deployer.lookupSupplier("words")).isNotNull();
|
||||
}
|
||||
|
||||
private String deploy(String jarName, String... args) throws Exception {
|
||||
Resource resource = new FileSystemResource(
|
||||
ArchiveUtils.getArchiveRoot(ArchiveUtils.getArchive(jarName)));
|
||||
AppDefinition definition = new AppDefinition(resource.getFilename(),
|
||||
Collections.emptyMap());
|
||||
AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,
|
||||
Collections.emptyMap(), Arrays.asList(args));
|
||||
String deployed = deployer.deploy(request);
|
||||
return deployed;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user