/* * Copyright 2013-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.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.ParentContextCloserApplicationListener; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.ContextIdApplicationContextInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 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.PassthruModuleOptionsMetadata; /** * A {@link Module} implementation backed by a Spring {@link ApplicationContext}. * * @author Mark Fisher * @author David Turanski * @author Gary Russell * @author Dave Syer * @author Ilayaperumal Gopinathan * @author Eric Bottard */ public abstract class SimpleModule extends AbstractModule { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private ConfigurableApplicationContext context; private final SpringApplicationBuilder application; private final AtomicInteger propertiesCounter = new AtomicInteger(); private final Properties properties = new Properties(); private final MutablePropertySources propertySources = new MutablePropertySources(); private ConfigurableApplicationContext parent; private final List<ApplicationListener<?>> listeners = new ArrayList<ApplicationListener<?>>(); private ModuleOptions moduleOptions; private final ClassLoader classLoader; public SimpleModule(ModuleDescriptor descriptor, ModuleDeploymentProperties deploymentProperties) { this(descriptor, deploymentProperties, null, defaultModuleOptions()); } public SimpleModule(ModuleDescriptor descriptor, ModuleDeploymentProperties deploymentProperties, ClassLoader classLoader, ModuleOptions moduleOptions) { super(descriptor, deploymentProperties); this.moduleOptions = moduleOptions; application = new SpringApplicationBuilder().sources(PropertyPlaceholderAutoConfiguration.class) .web(false).showBanner(false); this.classLoader = classLoader; if (classLoader != null) { application.resourceLoader(new PathMatchingResourcePatternResolver(classLoader)); } // Also add options as properties for now, b/c other parts of the system // (eg type conversion plugin) expects it this.properties.putAll(moduleOptionsToProperties(moduleOptions)); application.profiles(moduleOptions.profilesToActivate()); this.configureModuleApplicationContext((SimpleModuleDefinition) this.getDescriptor().getModuleDefinition()); } /** * Subclasses implement this method to configure the application context from sources contained in the * {@link org.springframework.xd.module.ModuleDefinition} */ protected abstract void configureModuleApplicationContext(SimpleModuleDefinition moduleDefinition); private Map<Object, Object> moduleOptionsToProperties(ModuleOptions moduleOptions) { Map<Object, Object> result = new HashMap<Object, Object>(); EnumerablePropertySource<?> ps = moduleOptions.asPropertySource(); for (String propname : ps.getPropertyNames()) { Object value = ps.getProperty(propname); if (value != null) { result.put(propname, value.toString()); } } return result; } @Override public void setParentContext(ApplicationContext parent) { this.parent = (ConfigurableApplicationContext) parent; } @Override public void addSource(Object source) { application.sources(source); } @Override public void addProperties(Properties properties) { this.registerPropertySource(properties); this.properties.putAll(properties); } @Override public void addListener(ApplicationListener<?> listener) { this.listeners.add(listener); } @Override public Properties getProperties() { return this.properties; } @Override public ConfigurableApplicationContext getApplicationContext() { return this.context; } @Override public <T> T getComponent(Class<T> requiredType) { return (this.context.isActive()) ? this.context.getBean(requiredType) : null; } public ClassLoader getClassLoader() { return classLoader; } @Override public <T> T getComponent(String componentName, Class<T> requiredType) { if (this.context.isActive() && this.context.containsBean(componentName)) { return context.getBean(componentName, requiredType); } return null; } private void registerPropertySource(Properties properties) { int propertiesIndex = this.propertiesCounter.getAndIncrement(); String propertySourceName = "properties-" + propertiesIndex; PropertySource<?> propertySource = new PropertiesPropertySource(propertySourceName, properties); this.propertySources.addLast(propertySource); } /** * Initialize this module by creating its application context, and provide it with a special * {@link org.springframework.core.env.Environment} that knows how to resolve module options placeholders. */ @Override public void initialize() { this.application.initializers(new ContextIdApplicationContextInitializer(this.toString())); ConfigurableEnvironment parentEnvironment = parent == null ? null : parent.getEnvironment(); ModuleEnvironment environment = new ModuleEnvironment(moduleOptions.asPropertySource(), parentEnvironment); for (PropertySource<?> source : propertySources) { environment.getPropertySources().addFirst(source); } this.application.parent(parent); this.application.environment(environment); if (this.listeners.size() > 0) { application.listeners(this.listeners.toArray(new ApplicationListener<?>[this.listeners.size()])); } this.application.listeners(new ModuleParentContextCloserApplicationListener(getDescriptor().getIndex())); if (this.classLoader != null) { final ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(this.classLoader); this.context = this.application.run(); } finally { Thread.currentThread().setContextClassLoader(defaultClassLoader); } } else { this.context = this.application.run(); } if (logger.isInfoEnabled()) { logger.info("initialized module: " + this.toString()); } } @Override public void start() { try { context.start(); } catch (BeansException be) { // Make sure the context is destroyed; this will allow possible destruction of life-cycle beans registered // (example: MBeans) destroy(); throw be; } } @Override public void stop() { if (context.isActive()) { context.stop(); // Shouldn't need to close() as well? } } @Override public boolean isRunning() { return context.isRunning(); } @Override public void destroy() { if (context instanceof DisposableBean) { try { ((DisposableBean) context).destroy(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new IllegalStateException(e); } } if (classLoader instanceof Closeable) { try { ((Closeable) classLoader).close(); } catch (IOException e) { } } } private static ModuleOptions defaultModuleOptions() { try { return new PassthruModuleOptionsMetadata().interpolate(Collections.<String, String> emptyMap()); } catch (BindException e) { throw new IllegalStateException(e); } } /** * Dedicated sublcass of {@link ParentContextCloserApplicationListener} used to create its own version of * ContextCloserListener that is aware of module order. Special care is taken so that no strong references to the * module context are retained (this is a *static* inner class). * * @author Eric Bottard */ private static final class ModuleParentContextCloserApplicationListener extends ParentContextCloserApplicationListener { private final int index; public ModuleParentContextCloserApplicationListener(int index) { this.index = index; } @Override protected ContextCloserListener createContextCloserListener(ConfigurableApplicationContext child) { return new ModuleContextCloserListener(child, index); } /** * Module context closer listener that sets the order based on the module deployment index. */ final static class ModuleContextCloserListener extends ContextCloserListener implements Ordered { private int index; public ModuleContextCloserListener(ConfigurableApplicationContext moduleContext, int index) { super(moduleContext); this.index = index; } @Override public int getOrder() { // Make sure producer modules get closed before the consumer modules (sink/processor) // by setting them the highest precedence. Smaller values come first. return index; } } } }