Java tutorial
/* * 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.dirt.module; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.OrderComparator; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.messaging.Message; import org.springframework.util.Assert; import org.springframework.validation.BindException; import org.springframework.xd.dirt.event.ModuleDeployedEvent; import org.springframework.xd.dirt.event.ModuleUndeployedEvent; import org.springframework.xd.dirt.plugins.job.JobPlugin; import org.springframework.xd.module.DeploymentMetadata; import org.springframework.xd.module.ModuleDefinition; import org.springframework.xd.module.ModuleType; import org.springframework.xd.module.core.CompositeModule; import org.springframework.xd.module.core.Module; import org.springframework.xd.module.core.Plugin; import org.springframework.xd.module.core.SimpleModule; 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.support.ParentLastURLClassLoader; import com.fasterxml.jackson.databind.ObjectMapper; /** * Listens for deployment request messages and instantiates {@link Module}s accordingly, applying {@link Plugin} logic * to them. * * @author Mark Fisher * @author Gary Russell * @author Ilayaperumal Gopinathan */ public class ModuleDeployer extends AbstractMessageHandler implements ApplicationContextAware, ApplicationEventPublisherAware, BeanClassLoaderAware, DisposableBean { private final Log logger = LogFactory.getLog(this.getClass()); private volatile ApplicationContext context; private volatile ApplicationContext globalContext; private volatile ApplicationEventPublisher eventPublisher; private final ObjectMapper mapper = new ObjectMapper(); private final ConcurrentMap<String, Map<Integer, Module>> deployedModules = new ConcurrentHashMap<String, Map<Integer, Module>>(); private volatile List<Plugin> plugins; private final ModuleDefinitionRepository moduleDefinitionRepository; private ClassLoader parentClassLoader; private final ModuleOptionsMetadataResolver moduleOptionsMetadataResolver; public ModuleDeployer(ModuleDefinitionRepository moduleDefinitionRepository, ModuleOptionsMetadataResolver moduleOptionsMetadataResolver) { Assert.notNull(moduleDefinitionRepository, "moduleDefinitionRepository must not be null"); Assert.notNull(moduleOptionsMetadataResolver, "moduleOptionsMetadataResolver must not be null"); this.moduleDefinitionRepository = moduleDefinitionRepository; this.moduleOptionsMetadataResolver = moduleOptionsMetadataResolver; } public Map<String, Map<Integer, Module>> getDeployedModules() { return deployedModules; } @Override public void setApplicationContext(ApplicationContext context) { this.context = context; this.globalContext = context.getParent().getParent(); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } @Override public void onInit() { this.plugins = new ArrayList<Plugin>(this.context.getParent().getBeansOfType(Plugin.class).values()); OrderComparator.sort(this.plugins); } @Override public void destroy() throws Exception { for (Entry<String, Map<Integer, Module>> entry : this.deployedModules.entrySet()) { if (logger.isDebugEnabled()) { logger.debug("Destroying group:" + entry.getKey()); } for (Entry<Integer, Module> moduleEntry : entry.getValue().entrySet()) { // fire module undeploy event to make sure the module event listeners // such as {@link ModuleEventStoreListener} are up-to-date. this.fireModuleUndeployedEvent(moduleEntry.getValue()); } } } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.parentClassLoader = classLoader; } @Override protected synchronized void handleMessageInternal(Message<?> message) throws Exception { String payloadString = message.getPayload().toString(); ModuleDeploymentRequest request = this.mapper.readValue(payloadString, ModuleDeploymentRequest.class); if (request.isRemove()) { handleUndeploy(request); } else if (request.isLaunch()) { Assert.isTrue(!(request instanceof CompositeModuleDeploymentRequest)); handleLaunch(request); } else { handleDeploy(request); } } /** * 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. */ private ModuleOptions safeModuleOptionsInterpolate(ModuleDeploymentRequest request) { String name = request.getModule(); ModuleType type = request.getType(); ModuleDefinition definition = this.moduleDefinitionRepository.findByNameAndType(name, type); Map<String, String> parameters = request.getParameters(); ModuleOptionsMetadata moduleOptionsMetadata = moduleOptionsMetadataResolver.resolve(definition); try { return moduleOptionsMetadata.interpolate(parameters); } catch (BindException e) { // Can't happen as parser should have already validated options throw new IllegalStateException(e); } } private void handleDeploy(ModuleDeploymentRequest request) { ModuleOptions moduleOptions = safeModuleOptionsInterpolate(request); Module module = createModule(request, moduleOptions); this.deployAndStore(module, request); } private Module createModule(ModuleDeploymentRequest request, ModuleOptions moduleOptions) { if (request instanceof CompositeModuleDeploymentRequest) { return createCompositeModule((CompositeModuleDeploymentRequest) request, moduleOptions); } else { return createSimpleModule(request, moduleOptions); } } private Module createCompositeModule(CompositeModuleDeploymentRequest compositeRequest, ModuleOptions moduleOptionsForComposite) { List<ModuleDeploymentRequest> children = compositeRequest.getChildren(); Assert.notEmpty(children, "child module list must not be empty"); List<Module> childrenModules = new ArrayList<Module>(children.size()); for (ModuleDeploymentRequest childRequest : children) { ModuleOptions narrowedModuleOptions = new PrefixNarrowingModuleOptions(moduleOptionsForComposite, childRequest.getModule()); childrenModules.add(createModule(childRequest, narrowedModuleOptions)); } String group = compositeRequest.getGroup(); int index = compositeRequest.getIndex(); String sourceChannelName = compositeRequest.getSourceChannelName(); String sinkChannelName = compositeRequest.getSinkChannelName(); DeploymentMetadata deploymentMetadata = new DeploymentMetadata(group, index, sourceChannelName, sinkChannelName); String moduleName = compositeRequest.getModule(); ModuleType moduleType = compositeRequest.getType(); return new CompositeModule(moduleName, moduleType, childrenModules, deploymentMetadata); } private Module createSimpleModule(ModuleDeploymentRequest request, ModuleOptions moduleOptions) { String group = request.getGroup(); int index = request.getIndex(); String name = request.getModule(); ModuleType type = request.getType(); ModuleDefinition definition = this.moduleDefinitionRepository.findByNameAndType(name, type); Assert.notNull(definition, "No moduleDefinition for " + name + ":" + type); DeploymentMetadata metadata = new DeploymentMetadata(group, index, request.getSourceChannelName(), request.getSinkChannelName()); ClassLoader classLoader = (definition.getClasspath() == null) ? null : new ParentLastURLClassLoader(definition.getClasspath(), parentClassLoader); Module module = new SimpleModule(definition, metadata, classLoader, moduleOptions); return module; } private void deployAndStore(Module module, ModuleDeploymentRequest request) { module.setParentContext(this.globalContext); this.deploy(module); if (logger.isInfoEnabled()) { logger.info("deployed " + module.toString()); } this.deployedModules.putIfAbsent(request.getGroup(), new HashMap<Integer, Module>()); this.deployedModules.get(request.getGroup()).put(request.getIndex(), module); } private void deploy(Module module) { this.preProcessModule(module); module.initialize(); this.postProcessModule(module); module.start(); this.fireModuleDeployedEvent(module); } private void handleUndeploy(ModuleDeploymentRequest request) { String group = request.getGroup(); Map<Integer, Module> modules = this.deployedModules.get(group); if (modules != null) { int index = request.getIndex(); Module module = modules.remove(index); if (modules.size() == 0) { this.deployedModules.remove(group); } if (module != null) { this.destroyModule(module); } else { if (logger.isDebugEnabled()) { logger.debug("Ignoring undeploy - module not deployed here: " + request); } } } else { if (logger.isTraceEnabled()) { logger.trace("Ignoring undeploy - group not deployed here: " + request); } } } private void destroyModule(Module module) { if (logger.isInfoEnabled()) { logger.info("removed " + module.toString()); } this.beforeShutdown(module); module.stop(); this.removeModule(module); module.destroy(); this.fireModuleUndeployedEvent(module); } private void handleLaunch(ModuleDeploymentRequest request) { String group = request.getGroup(); Map<Integer, Module> modules = this.deployedModules.get(group); if (modules != null) { processLaunchRequest(modules, request); } else { throw new ModuleNotDeployedException("Job launch"); } } private void processLaunchRequest(Map<Integer, Module> modules, ModuleDeploymentRequest request) { Module module = modules.get(request.getIndex()); // Since the request parameter may change on each launch request, // the request parameters are not added to module properties if (logger.isDebugEnabled()) { logger.debug("launching " + module.toString()); } launchModule(module, request.getParameters()); } /** * Get the list of supported plugins for the given module. * * @param module * @return list supported list of plugins */ private List<Plugin> getSupportedPlugins(Module module) { List<Plugin> supportedPlugins = new ArrayList<Plugin>(); if (this.plugins != null) { for (Plugin plugin : this.plugins) { if (plugin.supports(module)) { supportedPlugins.add(plugin); } } } return supportedPlugins; } /** * Allow plugins to contribute properties (e.g. "stream.name") calling module.addProperties(properties), etc. */ private void preProcessModule(Module module) { for (Plugin plugin : this.getSupportedPlugins(module)) { plugin.preProcessModule(module); } } /** * Allow plugins to perform other configuration after the module is initialized but before it is started. */ private void postProcessModule(Module module) { for (Plugin plugin : this.getSupportedPlugins(module)) { plugin.postProcessModule(module); } } private void removeModule(Module module) { for (Plugin plugin : this.getSupportedPlugins(module)) { plugin.removeModule(module); } } private void launchModule(Module module, Map<String, String> parameters) { if (this.plugins != null) { for (Plugin plugin : this.plugins) { // Currently, launching module is applicable only to Jobs if (plugin instanceof JobPlugin) { ((JobPlugin) plugin).launch(module, parameters); } } } } private void beforeShutdown(Module module) { for (Plugin plugin : this.getSupportedPlugins(module)) { plugin.beforeShutdown(module); } } private void fireModuleDeployedEvent(Module module) { if (this.eventPublisher != null) { ModuleDeployedEvent event = new ModuleDeployedEvent(module, this.context.getId()); event.setAttribute("group", module.getDeploymentMetadata().getGroup()); event.setAttribute("index", "" + module.getDeploymentMetadata().getIndex()); this.eventPublisher.publishEvent(event); } } private void fireModuleUndeployedEvent(Module module) { if (this.eventPublisher != null) { ModuleUndeployedEvent event = new ModuleUndeployedEvent(module, this.context.getId()); event.setAttribute("group", module.getDeploymentMetadata().getGroup()); event.setAttribute("index", "" + module.getDeploymentMetadata().getIndex()); this.eventPublisher.publishEvent(event); } } }