/*
* Copyright 2014 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.xd.module.core;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.xd.module.ModuleDeploymentProperties;
import org.springframework.xd.module.ModuleDescriptor;
import org.springframework.xd.module.SimpleModuleDefinition;
import org.springframework.xd.module.options.ModuleOptions;
import org.springframework.xd.module.options.ModuleOptionsMetadata;
import org.springframework.xd.module.options.ModuleOptionsMetadataResolver;
import org.springframework.xd.module.options.PrefixNarrowingModuleOptions;
import org.springframework.xd.module.support.ModuleUtils;
/**
* Determines the type of {@link Module} to create from the Module's metadata and creates a module instance. Also,
* resolves {@link org.springframework.xd.module.options.ModuleOptions} in the process.
*
* @author David Turanski
*/
public class ModuleFactory implements BeanClassLoaderAware, ResourceLoaderAware {
private static Log log = LogFactory.getLog(ModuleFactory.class);
private final ModuleOptionsMetadataResolver moduleOptionsMetadataResolver;
private volatile ClassLoader parentClassLoader = ModuleFactory.class.getClassLoader();
private ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
/**
* @param moduleOptionsMetadataResolver Used to bind configured {@link ModuleOptions} to {@link Module} instances
*/
public ModuleFactory(ModuleOptionsMetadataResolver moduleOptionsMetadataResolver) {
Assert.notNull(moduleOptionsMetadataResolver, "'moduleOptionsMetadataResolver'" + " cannot be null");
this.moduleOptionsMetadataResolver = moduleOptionsMetadataResolver;
}
/**
* Create a new {@link org.springframework.xd.module.core.SimpleModule} or
* {@link org.springframework.xd.module.core.CompositeModule} instance from inspecting the
* {@link org.springframework.xd.module.ModuleDescriptor}, particularly the descriptor's
* {@link org.springframework.xd.module.ModuleDefinition}.
*
* @param moduleDescriptor contains the module's runtime configuration (required)
* @param deploymentProperties contains deployment properties (may be null)
* @return the module instance
*/
public Module createModule(ModuleDescriptor moduleDescriptor, ModuleDeploymentProperties deploymentProperties) {
ModuleOptions moduleOptions = this.safeModuleOptionsInterpolate(moduleDescriptor);
Module module = createAndConfigureModuleInstance(moduleDescriptor, moduleOptions, deploymentProperties);
return module;
}
/**
* Creates and configures a {@link org.springframework.xd.module.core.Module} after resolving {@link
* org.springframework.xd.module.options.ModuleOptions}. createComposedModule() calls this for each component
* module.
*
* @param moduleDescriptor
* @param moduleOptions
* @param deploymentProperties
* @return the module instance
*/
private Module createAndConfigureModuleInstance(ModuleDescriptor moduleDescriptor, ModuleOptions moduleOptions,
ModuleDeploymentProperties deploymentProperties) {
Module module = moduleDescriptor.isComposed() ?
createCompositeModule(moduleDescriptor, moduleOptions, deploymentProperties) :
createSimpleModule(moduleDescriptor, moduleOptions, deploymentProperties);
return module;
}
/**
* Create a simple module based on the provided {@link ModuleDescriptor}, {@link ModuleOptions}, and {@link ModuleDeploymentProperties}.
*
* @param moduleDescriptor descriptor for the composed module
* @param moduleOptions module options for the composed module
* @param deploymentProperties deployment related properties for the composed module
* @return new simple module instance
*/
private Module createSimpleModule(ModuleDescriptor moduleDescriptor, ModuleOptions moduleOptions,
ModuleDeploymentProperties deploymentProperties) {
if (log.isInfoEnabled()) {
log.info("creating simple module " + moduleDescriptor);
}
SimpleModuleDefinition definition = (SimpleModuleDefinition) moduleDescriptor.getModuleDefinition();
Resource moduleLocation = resourceLoader.getResource(definition.getLocation());
ClassLoader moduleClassLoader = ModuleUtils.createModuleClassLoader(moduleLocation, this.parentClassLoader);
Class<? extends SimpleModule> moduleClass = determineModuleClass((SimpleModuleDefinition) moduleDescriptor.getModuleDefinition(), moduleClassLoader);
Assert.notNull(moduleClass,
String.format("cannot create module '%s:%s' from module definition.", moduleDescriptor.getModuleName(),
moduleDescriptor.getType()));
return SimpleModuleCreator
.createModule(moduleDescriptor, deploymentProperties, moduleClassLoader, moduleOptions, moduleClass);
}
private Class<? extends SimpleModule> determineModuleClass(SimpleModuleDefinition moduleDefinition, ClassLoader moduleClassLoader) {
if (ResourceConfiguredModule.resourceBasedConfigurationFile(moduleDefinition, moduleClassLoader) != null) {
return ResourceConfiguredModule.class;
}
else if (JavaConfiguredModule.basePackages(moduleDefinition, moduleClassLoader).length > 0) {
return JavaConfiguredModule.class;
}
return null;
}
/**
* Create a composite module based on the provided {@link ModuleDescriptor},
* {@link org.springframework.xd.module.options.ModuleOptions}, and
* {@link org.springframework.xd.module.ModuleDeploymentProperties}.
*
* @param compositeDescriptor descriptor for the composed module
* @param options module options for the composed module
* @param deploymentProperties deployment related properties for the composed module
* @return new composed module instance
* @see ModuleDescriptor#isComposed
*/
private Module createCompositeModule(ModuleDescriptor compositeDescriptor, ModuleOptions options,
ModuleDeploymentProperties deploymentProperties) {
List<ModuleDescriptor> children = compositeDescriptor.getChildren();
Assert.notEmpty(children, "child module list must not be empty");
if (log.isInfoEnabled()) {
log.info("creating composite module " + compositeDescriptor);
}
List<Module> childrenModules = new ArrayList<Module>(children.size());
for (ModuleDescriptor moduleDescriptor : children) {
ModuleOptions moduleOptions = new PrefixNarrowingModuleOptions(options, moduleDescriptor.getModuleName());
// due to parser results being reversed, we add each at index 0
// todo: is it right to pass the composite deploymentProperties here?
childrenModules
.add(0, createAndConfigureModuleInstance(moduleDescriptor, moduleOptions, deploymentProperties));
}
return new CompositeModule(compositeDescriptor, deploymentProperties, childrenModules);
}
/**
* Takes a request and returns an instance of {@link ModuleOptions} bound with the request parameters. Binding is
* assumed to not fail, as it has already been validated on the admin side.
*
* @param descriptor module descriptor for which to bind request parameters
* @return module options bound with request parameters
*/
private ModuleOptions safeModuleOptionsInterpolate(ModuleDescriptor descriptor) {
Map<String, String> parameters = descriptor.getParameters();
ModuleOptionsMetadata moduleOptionsMetadata =
moduleOptionsMetadataResolver.resolve(descriptor.getModuleDefinition());
try {
return moduleOptionsMetadata.interpolate(parameters);
}
catch (BindException e) {
// Can't happen as parser should have already validated options
throw new IllegalStateException(e);
}
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.parentClassLoader = classLoader;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = (ResourcePatternResolver) resourceLoader;
}
static class SimpleModuleCreator {
public static <T extends SimpleModule> T createModule(ModuleDescriptor descriptor,
ModuleDeploymentProperties deploymentProperties, ClassLoader classLoader, ModuleOptions moduleOptions,
Class<T> requiredType) {
Constructor<T> constructor = null;
try {
constructor = requiredType
.getConstructor(ModuleDescriptor.class, ModuleDeploymentProperties.class, ClassLoader.class,
ModuleOptions.class);
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
return constructor.newInstance(descriptor, deploymentProperties, classLoader, moduleOptions);
}
catch (InstantiationException e) {
throw new RuntimeException(e);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}