Added new deployer experimental module
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2019-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import org.springframework.cloud.function.context.FunctionCatalog;
|
||||
import org.springframework.cloud.function.context.catalog.FunctionInspector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class ApplicationContainer {
|
||||
|
||||
private final FunctionCatalog functionCatalog;
|
||||
|
||||
private final FunctionInspector functionInspector;
|
||||
|
||||
private final FunctionProperties functionProperties;
|
||||
|
||||
public ApplicationContainer(FunctionCatalog functionCatalog,
|
||||
FunctionInspector functionInspector, FunctionProperties functionProperties) {
|
||||
this.functionCatalog = functionCatalog;
|
||||
this.functionInspector = functionInspector;
|
||||
this.functionProperties = functionProperties;
|
||||
}
|
||||
|
||||
protected FunctionCatalog getFunctionCatalog() {
|
||||
return this.functionCatalog;
|
||||
}
|
||||
|
||||
protected FunctionInspector getFunctionInspector() {
|
||||
return this.functionInspector;
|
||||
}
|
||||
|
||||
protected FunctionProperties getFunctionProperties() {
|
||||
return this.functionProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2019-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class DeployerContextUtils {
|
||||
|
||||
public static Type findType(BeanFactory beanFactory, String name) {
|
||||
ConfigurableListableBeanFactory registry = (ConfigurableListableBeanFactory) beanFactory;
|
||||
AbstractBeanDefinition definition = (AbstractBeanDefinition) registry.getBeanDefinition(name);
|
||||
|
||||
Object source = definition.getSource();
|
||||
|
||||
Type param = null;
|
||||
if (source instanceof MethodMetadata) {
|
||||
param = findBeanType(definition, ((MethodMetadata) source).getDeclaringClassName(), ((MethodMetadata) source).getMethodName());
|
||||
}
|
||||
else if (source instanceof Resource) {
|
||||
param = registry.getType(name);
|
||||
}
|
||||
else {
|
||||
ResolvableType type = (ResolvableType) getField(definition, "targetType");
|
||||
if (type != null) {
|
||||
param = type.getType();
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
private static Type findBeanType(AbstractBeanDefinition definition, String declaringClassName, String methodName) {
|
||||
Class<?> factory = ClassUtils.resolveClassName(declaringClassName, null);
|
||||
Class<?>[] params = getParamTypes(factory, definition);
|
||||
Method method = ReflectionUtils.findMethod(factory, methodName,
|
||||
params);
|
||||
Type type = method.getGenericReturnType();
|
||||
return type;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParamTypes(Class<?> factory,
|
||||
AbstractBeanDefinition definition) {
|
||||
if (definition instanceof RootBeanDefinition) {
|
||||
RootBeanDefinition root = (RootBeanDefinition) definition;
|
||||
for (Method method : getCandidateMethods(factory, root)) {
|
||||
if (root.isFactoryMethod(method)) {
|
||||
return method.getParameterTypes();
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Class<?>> params = new ArrayList<>();
|
||||
for (ConstructorArgumentValues.ValueHolder holder : definition
|
||||
.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
|
||||
params.add(ClassUtils.resolveClassName(holder.getType(), null));
|
||||
}
|
||||
return params.toArray(new Class<?>[0]);
|
||||
}
|
||||
|
||||
private static Method[] getCandidateMethods(final Class<?> factoryClass,
|
||||
final RootBeanDefinition mbd) {
|
||||
if (System.getSecurityManager() != null) {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
|
||||
@Override
|
||||
public Method[] run() {
|
||||
return (mbd.isNonPublicAccessAllowed()
|
||||
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
|
||||
: factoryClass.getMethods());
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return (mbd.isNonPublicAccessAllowed()
|
||||
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
|
||||
: factoryClass.getMethods());
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getField(Object target, String name) {
|
||||
Field field = ReflectionUtils.findField(target.getClass(), name);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return ReflectionUtils.getField(field, target);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2019-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.loader.JarLauncher;
|
||||
import org.springframework.boot.loader.LaunchedURLClassLoader;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
import org.springframework.cloud.function.context.FunctionRegistration;
|
||||
import org.springframework.cloud.function.context.FunctionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
class ExternalFunctionJarLauncher extends JarLauncher {
|
||||
|
||||
private static Log logger = LogFactory.getLog(ExternalFunctionJarLauncher.class);
|
||||
|
||||
private final StandardEvaluationContext evalContext = new StandardEvaluationContext();
|
||||
|
||||
private final Archive archive;
|
||||
|
||||
ExternalFunctionJarLauncher(Archive archive) {
|
||||
super(archive);
|
||||
this.archive = archive;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
protected void deploy(ApplicationContext deployerContext, String[] args) {
|
||||
|
||||
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
this.launch(deployerContext, args);
|
||||
Map<String, Object> functions = this.discoverFunctions();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discovered functions: " + functions);
|
||||
}
|
||||
FunctionRegistry functionRegistry = deployerContext.getBean(FunctionRegistry.class);
|
||||
for (Entry<String, Object> entry : functions.entrySet()) {
|
||||
FunctionRegistration registration = new FunctionRegistration(entry.getValue(), entry.getKey());
|
||||
Type type = this.findType(entry.getKey());
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Registering function '" + entry.getKey() + "' of type '" + type
|
||||
+ "' in FunctionRegistry.");
|
||||
}
|
||||
registration.type(type);
|
||||
functionRegistry.register(registration);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to deploy archive " + archive, e);
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(currentLoader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
String className = DeployerContextUtils.class.getName();
|
||||
String classAsPath = className.replace('.', '/') + ".class";
|
||||
byte[] fcuBytes = StreamUtils
|
||||
.copyToByteArray(DeployerContextUtils.class.getClassLoader().getResourceAsStream(classAsPath));
|
||||
/*
|
||||
* While LaunchedURLClassLoader is completely disconnected with the current
|
||||
* class loader, this will still allow it to see FunctionContextUtils
|
||||
*/
|
||||
return new ClassLoader(new LaunchedURLClassLoader(urls, null)) {
|
||||
boolean functionContextUtilsLoaded;
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||
if (!functionContextUtilsLoaded && className.equals(name)) {
|
||||
Class<?> fcuClass = defineClass(name, fcuBytes, 0, fcuBytes.length);
|
||||
this.functionContextUtilsLoaded = true;
|
||||
return fcuClass;
|
||||
}
|
||||
return super.findClass(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void launch(ApplicationContext deployerContext, String[] args) throws Exception {
|
||||
JarFile.registerUrlProtocolHandler();
|
||||
Thread.currentThread().setContextClassLoader(createClassLoader(getClassPathArchives()));
|
||||
evalContext.setTypeLocator(new StandardTypeLocator(Thread.currentThread().getContextClassLoader()));
|
||||
|
||||
String mainClassName = getMainClass();
|
||||
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(mainClassName);
|
||||
|
||||
Class<?> bootAppClass = Thread.currentThread().getContextClassLoader()
|
||||
.loadClass(SpringApplication.class.getName());
|
||||
Method runMethod = bootAppClass.getDeclaredMethod("run", Class.class, String[].class);
|
||||
Object applicationContext = runMethod.invoke(null, mainClass, (Object) args);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Application context for archive '" + archive.getUrl() + "' is created.");
|
||||
}
|
||||
evalContext.setVariable("context", applicationContext);
|
||||
setBeanFactory(applicationContext);
|
||||
}
|
||||
|
||||
private void setBeanFactory(Object applicationContext) throws Exception {
|
||||
Expression parsed = new SpelExpressionParser().parseExpression("#context.getBeanFactory()");
|
||||
Object beanFactory = parsed.getValue(evalContext);
|
||||
evalContext.setVariable("bf", beanFactory);
|
||||
}
|
||||
|
||||
private Type findType(String name) {
|
||||
evalContext.setVariable("functionName", name);
|
||||
String expr = "T(" + DeployerContextUtils.class.getName() + ").findType(#bf, #functionName)";
|
||||
Expression parsed = new SpelExpressionParser().parseExpression(expr);
|
||||
Object type = parsed.getValue(evalContext);
|
||||
return (Type) type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> discoverFunctions() throws Exception {
|
||||
Map<String, Object> allFunctions = new HashMap<String, Object>();
|
||||
Expression parsed = new SpelExpressionParser()
|
||||
.parseExpression("#context.getBeansOfType(T(java.util.function.Function))");
|
||||
allFunctions.putAll((Map<String, Object>) parsed.getValue(evalContext));
|
||||
parsed = new SpelExpressionParser().parseExpression("#context.getBeansOfType(T(java.util.function.Supplier))");
|
||||
allFunctions.putAll((Map<String, Object>) parsed.getValue(evalContext));
|
||||
parsed = new SpelExpressionParser().parseExpression("#context.getBeansOfType(T(java.util.function.Consumer))");
|
||||
allFunctions.putAll((Map<String, Object>) parsed.getValue(evalContext));
|
||||
return allFunctions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2019-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.cloud.function.context.FunctionCatalog;
|
||||
import org.springframework.cloud.function.context.catalog.FunctionInspector;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(FunctionProperties.class)
|
||||
public class FunctionDeployerBootstrap implements ApplicationContextAware {
|
||||
|
||||
public static FunctionDeployerBootstrap instance(String... args) {
|
||||
ApplicationContext context = SpringApplication.run(FunctionDeployerBootstrap.class, args);
|
||||
return context.getBean(FunctionDeployerBootstrap.class);
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private FunctionProperties functionProperties;
|
||||
|
||||
@Autowired
|
||||
private FunctionCatalog functionCatalog;
|
||||
|
||||
@Autowired
|
||||
private FunctionInspector functionInspector;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ApplicationContainer> T run(Class<?> configurationClass, String... args) {
|
||||
|
||||
try {
|
||||
Archive archive = new JarFileArchive(new File(functionProperties.getLocation()));
|
||||
ExternalFunctionJarLauncher launcher = new ExternalFunctionJarLauncher(archive);
|
||||
launcher.deploy(this.applicationContext, args);
|
||||
|
||||
Constructor<? extends ApplicationContainer> applicationContainerCtr = (Constructor<? extends ApplicationContainer>) configurationClass
|
||||
.getDeclaredConstructor(FunctionCatalog.class, FunctionInspector.class, FunctionProperties.class);
|
||||
|
||||
ApplicationContainer applicationContainer = applicationContainerCtr.newInstance(this.functionCatalog,
|
||||
this.functionInspector, this.functionProperties);
|
||||
return (T) applicationContainer;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to launch archive: " + functionProperties.getLocation(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration properties for deciding how to locate the functional class to execute.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.function")
|
||||
public class FunctionProperties {
|
||||
|
||||
/**
|
||||
* Location(s) of jar archives containing the supplier/function/consumer class to run.
|
||||
*/
|
||||
private String location;
|
||||
|
||||
private String functionName;
|
||||
|
||||
public void setFunctionName(String functionName) {
|
||||
this.functionName = functionName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.functionName;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Assert.notNull(this.location, "No archive location provided, please configure spring.cloud.function.location as a jar or directory.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.deployer.sample;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.cloud.function.context.FunctionCatalog;
|
||||
import org.springframework.cloud.function.context.catalog.FunctionInspector;
|
||||
import org.springframework.cloud.function.deployer.ApplicationContainer;
|
||||
import org.springframework.cloud.function.deployer.FunctionDeployerBootstrap;
|
||||
import org.springframework.cloud.function.deployer.FunctionProperties;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SampleInvoker extends ApplicationContainer {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SampleInvoker invoker = FunctionDeployerBootstrap.instance(
|
||||
"--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/target/simple-function-app-0.0.1-SNAPSHOT.jar",
|
||||
"--spring.cloud.function.function-name=uppercase")
|
||||
.run(SampleInvoker.class, args);
|
||||
|
||||
System.out.println(invoker.uppercase("eric"));
|
||||
System.out.println(invoker.uppercase("oleg"));
|
||||
}
|
||||
|
||||
private Function<String, String> function;
|
||||
|
||||
public SampleInvoker(FunctionCatalog functionCatalog, FunctionInspector functionInspector,
|
||||
FunctionProperties functionProperties) {
|
||||
super(functionCatalog, functionInspector, functionProperties);
|
||||
this.function = this.getFunctionCatalog().lookup(functionProperties.getName());
|
||||
}
|
||||
|
||||
public String uppercase(String value) {
|
||||
return this.function.apply(value);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user