/* * 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; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.router.AbstractMappingMessageRouter; import org.springframework.integration.support.context.NamedComponent; import org.springframework.integration.support.management.MappingMessageRouterManagement; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * The {@link AbstractRouterSpec} for an {@link AbstractMappingMessageRouter}. * * @param <K> the key type. * @param <R> the {@link AbstractMappingMessageRouter} implementation type. * * @author Artem Bilan * * @since 5.0 */ public final class RouterSpec<K, R extends AbstractMappingMessageRouter> extends AbstractRouterSpec<RouterSpec<K, R>, R> implements ComponentsRegistration { private final RouterMappingProvider mappingProvider; private String prefix; private String suffix; private boolean mappingProviderRegistered; RouterSpec(R router) { super(router); this.mappingProvider = new RouterMappingProvider(this.handler); } /** * @param resolutionRequired the resolutionRequired. * @return the router spec. * @see AbstractMappingMessageRouter#setResolutionRequired(boolean) */ public RouterSpec<K, R> resolutionRequired(boolean resolutionRequired) { this.handler.setResolutionRequired(resolutionRequired); return _this(); } /** * Set a limit for how many dynamic channels are retained (for reporting purposes). * When the limit is exceeded, the oldest channel is discarded. * <p><b>NOTE: this does not affect routing, just the reporting which dynamically * resolved channels have been routed to.</b> Default {@code 100}. * @param dynamicChannelLimit the limit. * @return the router spec. * @see AbstractMappingMessageRouter#setDynamicChannelLimit(int) */ public RouterSpec<K, R> dynamicChannelLimit(int dynamicChannelLimit) { this.handler.setDynamicChannelLimit(dynamicChannelLimit); return _this(); } /** * Cannot be invoked if {@link #subFlowMapping(Object, IntegrationFlow)} is used. * @param prefix the prefix. * @return the router spec. * @see AbstractMappingMessageRouter#setPrefix(String) */ public RouterSpec<K, R> prefix(String prefix) { Assert.state(this.componentsToRegister.isEmpty(), "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); this.prefix = prefix; this.handler.setPrefix(prefix); return _this(); } /** * Cannot be invoked if {@link #subFlowMapping(Object, IntegrationFlow)} is used. * @param suffix the suffix to set. * @return the router spec. * @see AbstractMappingMessageRouter#setSuffix(String) */ public RouterSpec<K, R> suffix(String suffix) { Assert.state(this.componentsToRegister.isEmpty(), "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); this.suffix = suffix; this.handler.setSuffix(suffix); return _this(); } /** * @param key the key. * @param channelName the channelName. * @return the router spec. * @see AbstractMappingMessageRouter#setChannelMapping(String, String) */ public RouterSpec<K, R> channelMapping(K key, final String channelName) { Assert.notNull(key, "'key' must not be null"); Assert.hasText(channelName, "'channelName' must not be null"); if (key instanceof String) { this.handler.setChannelMapping((String) key, channelName); } else { this.mappingProvider.addMapping(key, new NamedComponent() { @Override public String getComponentName() { return channelName; } @Override public String getComponentType() { return "channel"; } }); } return _this(); } /** * Add a subflow as an alternative to a {@link #channelMapping(Object, String)}. * {@link #prefix(String)} and {@link #suffix(String)} cannot be used when subflow * mappings are used. * @param key the key. * @param subFlow the subFlow. * @return the router spec. */ public RouterSpec<K, R> subFlowMapping(K key, IntegrationFlow subFlow) { Assert.notNull(key, "'key' must not be null"); Assert.notNull(subFlow, "'subFlow' must not be null"); Assert.state(!(StringUtils.hasText(this.prefix) || StringUtils.hasText(this.suffix)), "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); DirectChannel channel = new DirectChannel(); IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(channel); subFlow.configure(flowBuilder); this.componentsToRegister.add(flowBuilder); this.mappingProvider.addMapping(key, channel); return _this(); } @Override public Collection<Object> getComponentsToRegister() { // The 'mappingProvider' must be added to the 'componentsToRegister' in the end to // let all other components to be registered before the 'RouterMappingProvider.onInit()' logic. if (!this.mappingProviderRegistered) { if (!this.mappingProvider.mapping.isEmpty()) { this.componentsToRegister.add(this.mappingProvider); } this.mappingProviderRegistered = true; } return super.getComponentsToRegister(); } private static class RouterMappingProvider extends IntegrationObjectSupport implements ApplicationListener<ContextRefreshedEvent> { private final AtomicBoolean initialized = new AtomicBoolean(); private final MappingMessageRouterManagement router; private final Map<Object, NamedComponent> mapping = new HashMap<Object, NamedComponent>(); RouterMappingProvider(MappingMessageRouterManagement router) { this.router = router; } void addMapping(Object key, NamedComponent channel) { this.mapping.put(key, channel); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext() == getApplicationContext() && !this.initialized.getAndSet(true)) { ConversionService conversionService = getConversionService(); if (conversionService == null) { conversionService = DefaultConversionService.getSharedInstance(); } for (Map.Entry<Object, NamedComponent> entry : this.mapping.entrySet()) { Object key = entry.getKey(); String channelKey; if (key instanceof String) { channelKey = (String) key; } else if (key instanceof Class) { channelKey = ((Class<?>) key).getName(); } else if (conversionService.canConvert(key.getClass(), String.class)) { channelKey = conversionService.convert(key, String.class); } else { throw new MessagingException("Unsupported channel mapping type for router [" + key.getClass() + "]"); } this.router.setChannelMapping(channelKey, entry.getValue().getComponentName()); } } } } }