/* * 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.integration.dsl.context; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.support.context.NamedComponent; import org.springframework.messaging.MessageChannel; import org.springframework.util.Assert; /** * A public API for dynamic (manual) registration of {@link IntegrationFlow}, * not via standard bean registration phase. * <p> * The bean of this component is provided via framework automatically. * A bean name is based on the decapitalized class name. * It must be injected to the target service before use. * <p> * The typical use-case, and, therefore algorithm, is: * <ul> * <li> create {@link IntegrationFlow} depending of the business logic * <li> register that {@link IntegrationFlow} in this {@link IntegrationFlowContext}, * with optional {@code id} and {@code autoStartup} flag * <li> obtain a {@link MessagingTemplate} for that {@link IntegrationFlow} * (if it is started from the {@link MessageChannel}) and send (or send-and-receive) * messages to the {@link IntegrationFlow} * <li> remove the {@link IntegrationFlow} by its {@code id} from this {@link IntegrationFlowContext} * </ul> * <p> * For convenience an associated {@link IntegrationFlowRegistration} is returned after registration. * It can be used for access to the target {@link IntegrationFlow} or for manipulation with its lifecycle. * * @author Artem Bilan * * @since 5.0 * * @see IntegrationFlowRegistration */ public final class IntegrationFlowContext implements BeanFactoryAware { private final Map<String, IntegrationFlowRegistration> registry = new HashMap<>(); private ConfigurableListableBeanFactory beanFactory; private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor; private IntegrationFlowContext() { } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, "To use Spring Integration Java DSL the 'beanFactory' has to be an instance of " + "'ConfigurableListableBeanFactory'. " + "Consider using 'GenericApplicationContext' implementation."); this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor(); this.autowiredAnnotationBeanPostProcessor.setBeanFactory(this.beanFactory); } /** * Associate provided {@link IntegrationFlow} with an {@link IntegrationFlowRegistrationBuilder} * for additional options and farther registration in the application context. * @param integrationFlow the {@link IntegrationFlow} to register * @return the IntegrationFlowRegistrationBuilder associated with the provided {@link IntegrationFlow} */ public IntegrationFlowRegistrationBuilder registration(IntegrationFlow integrationFlow) { return new IntegrationFlowRegistrationBuilder(integrationFlow); } private void register(IntegrationFlowRegistrationBuilder builder) { IntegrationFlow integrationFlow = builder.integrationFlowRegistration.getIntegrationFlow(); String flowId = builder.integrationFlowRegistration.getId(); if (flowId == null) { flowId = generateBeanName(integrationFlow, null); builder.id(flowId); } IntegrationFlow theFlow = (IntegrationFlow) registerBean(integrationFlow, flowId, null); builder.integrationFlowRegistration.setIntegrationFlow(theFlow); final String theFlowId = flowId; builder.additionalBeans.forEach((key, value) -> registerBean(key, value, theFlowId)); if (builder.autoStartup) { builder.integrationFlowRegistration.start(); } this.registry.put(flowId, builder.integrationFlowRegistration); } private Object registerBean(Object bean, String beanName, String parentName) { if (beanName == null) { beanName = generateBeanName(bean, parentName); } this.autowiredAnnotationBeanPostProcessor.processInjection(bean); bean = this.beanFactory.initializeBean(bean, beanName); this.beanFactory.registerSingleton(beanName, bean); if (parentName != null) { this.beanFactory.registerDependentBean(parentName, beanName); } if (bean instanceof DisposableBean) { ((DefaultSingletonBeanRegistry) this.beanFactory) .registerDisposableBean(beanName, (DisposableBean) bean); } return bean; } /** * Obtain an {@link IntegrationFlowRegistration} for the {@link IntegrationFlow} * associated with the provided {@code flowId}. * @param flowId the bean name to obtain * @return the IntegrationFlowRegistration for provided {@code id} or {@code null} */ public IntegrationFlowRegistration getRegistrationById(String flowId) { return this.registry.get(flowId); } /** * Destroy an {@link IntegrationFlow} bean (as well as all its dependant beans) * for provided {@code flowId} and clean up all the local cache for it. * @param flowId the bean name to destroy from */ public synchronized void remove(String flowId) { if (this.registry.containsKey(flowId)) { IntegrationFlowRegistration flowRegistration = this.registry.remove(flowId); flowRegistration.stop(); ((DefaultSingletonBeanRegistry) this.beanFactory).destroySingleton(flowId); } else { throw new IllegalStateException("Only manually registered IntegrationFlows can be removed. " + "But [" + flowId + "] ins't one of them."); } } /** * Obtain a {@link MessagingTemplate} with its default destination set to the input channel * of the {@link IntegrationFlow} for provided {@code flowId}. * <p> Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method. * <p> If {@link IntegrationFlow} doesn't start with the {@link MessageChannel}, the * {@link IllegalStateException} is thrown. * @param flowId the bean name to obtain the input channel from * @return the {@link MessagingTemplate} instance */ public MessagingTemplate messagingTemplateFor(String flowId) { return this.registry.get(flowId) .getMessagingTemplate(); } /** * Provide the state of the mapping of integration flow names to their * {@link IntegrationFlowRegistration} instances. * @return the registry of flow ids and their registration. */ public Map<String, IntegrationFlowRegistration> getRegistry() { return Collections.unmodifiableMap(this.registry); } private String generateBeanName(Object instance, String parentName) { if (instance instanceof NamedComponent && ((NamedComponent) instance).getComponentName() != null) { return ((NamedComponent) instance).getComponentName(); } String generatedBeanName = (parentName != null ? parentName : "") + instance.getClass().getName(); String id = generatedBeanName; int counter = -1; while (counter == -1 || this.beanFactory.containsBean(id)) { counter++; id = generatedBeanName + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + counter; } return id; } /** * A Builder pattern implementation for the options to register {@link IntegrationFlow} * in the application context. */ public final class IntegrationFlowRegistrationBuilder { private Map<Object, String> additionalBeans = new HashMap<>(); private final IntegrationFlowRegistration integrationFlowRegistration; private boolean autoStartup = true; IntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) { this.integrationFlowRegistration = new IntegrationFlowRegistration(integrationFlow); this.integrationFlowRegistration.setBeanFactory(IntegrationFlowContext.this.beanFactory); this.integrationFlowRegistration.setIntegrationFlowContext(IntegrationFlowContext.this); } public IntegrationFlowRegistrationBuilder id(String id) { this.integrationFlowRegistration.setId(id); return this; } public IntegrationFlowRegistrationBuilder autoStartup(boolean autoStartup) { this.autoStartup = autoStartup; return this; } public IntegrationFlowRegistrationBuilder addBean(Object bean) { return addBean(null, bean); } public IntegrationFlowRegistrationBuilder addBean(String name, Object bean) { this.additionalBeans.put(bean, name); return this; } public IntegrationFlowRegistration register() { IntegrationFlowContext.this.register(this); return this.integrationFlowRegistration; } } }