/* * Copyright 2014-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.config; import java.beans.Introspector; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.springframework.beans.BeanMetadataAttribute; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.annotation.AnnotationConstants; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.gateway.GatewayMethodMetadata; import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * The {@link ImportBeanDefinitionRegistrar} to parse {@link MessagingGateway} and its {@code service-interface} * and to register {@link BeanDefinition} {@link GatewayProxyFactoryBean}. * * @author Artem Bilan * @author Gary Russell * @author Andy Wilksinson * * @since 4.0 */ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) { Assert.isTrue(importingClassMetadata.isInterface(), "@MessagingGateway can only be specified on an interface"); List<MultiValueMap<String, Object>> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata); Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName()); replaceEmptyOverrides(valuesHierarchy, annotationAttributes); annotationAttributes.put("serviceInterface", importingClassMetadata.getClassName()); BeanDefinitionReaderUtils.registerBeanDefinition(this.parse(annotationAttributes), registry); } } public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes) { String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression"); @SuppressWarnings("unchecked") Map<String, Object>[] defaultHeaders = (Map<String, Object>[]) gatewayAttributes.get("defaultHeaders"); String defaultRequestChannel = (String) gatewayAttributes.get("defaultRequestChannel"); String defaultReplyChannel = (String) gatewayAttributes.get("defaultReplyChannel"); String errorChannel = (String) gatewayAttributes.get("errorChannel"); String asyncExecutor = (String) gatewayAttributes.get("asyncExecutor"); String mapper = (String) gatewayAttributes.get("mapper"); boolean hasMapper = StringUtils.hasText(mapper); boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression); Assert.state(!hasMapper || !hasDefaultPayloadExpression, "'defaultPayloadExpression' is not allowed when a 'mapper' is provided"); boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders); Assert.state(!hasMapper || !hasDefaultHeaders, "'defaultHeaders' are not allowed when a 'mapper' is provided"); BeanDefinitionBuilder gatewayProxyBuilder = BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyFactoryBean.class); if (hasDefaultHeaders || hasDefaultPayloadExpression) { BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class); if (hasDefaultPayloadExpression) { methodMetadataBuilder.addPropertyValue("payloadExpression", defaultPayloadExpression); } Map<String, Object> headerExpressions = new ManagedMap<String, Object>(); for (Map<String, Object> header : defaultHeaders) { String headerValue = (String) header.get("value"); String headerExpression = (String) header.get("expression"); boolean hasValue = StringUtils.hasText(headerValue); if (hasValue == StringUtils.hasText(headerExpression)) { throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " + "is required on a gateway's header."); } BeanDefinition expressionDef = new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class); expressionDef.getConstructorArgumentValues() .addGenericArgumentValue(hasValue ? headerValue : headerExpression); headerExpressions.put((String) header.get("name"), expressionDef); } methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions); gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition()); } if (StringUtils.hasText(defaultRequestChannel)) { gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel); } if (StringUtils.hasText(defaultReplyChannel)) { gatewayProxyBuilder.addPropertyValue("defaultReplyChannelName", defaultReplyChannel); } if (StringUtils.hasText(errorChannel)) { gatewayProxyBuilder.addPropertyValue("errorChannelName", errorChannel); } if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) { gatewayProxyBuilder.addPropertyValue("asyncExecutor", null); } else if (StringUtils.hasText(asyncExecutor)) { gatewayProxyBuilder.addPropertyReference("asyncExecutor", asyncExecutor); } if (StringUtils.hasText(mapper)) { gatewayProxyBuilder.addPropertyReference("mapper", mapper); } gatewayProxyBuilder.addPropertyValue("defaultRequestTimeout", gatewayAttributes.get("defaultRequestTimeout")); gatewayProxyBuilder.addPropertyValue("defaultReplyTimeout", gatewayAttributes.get("defaultReplyTimeout")); gatewayProxyBuilder.addPropertyValue("methodMetadataMap", gatewayAttributes.get("methods")); String serviceInterface = (String) gatewayAttributes.get("serviceInterface"); if (!StringUtils.hasText(serviceInterface)) { serviceInterface = "org.springframework.integration.gateway.RequestReplyExchanger"; } String id = (String) gatewayAttributes.get("name"); if (!StringUtils.hasText(id)) { id = Introspector.decapitalize(serviceInterface.substring(serviceInterface.lastIndexOf(".") + 1)); } gatewayProxyBuilder.addConstructorArgValue(serviceInterface); AbstractBeanDefinition beanDefinition = gatewayProxyBuilder.getBeanDefinition(); beanDefinition.addMetadataAttribute(new BeanMetadataAttribute(IntegrationConfigUtils.FACTORY_BEAN_OBJECT_TYPE, serviceInterface)); return new BeanDefinitionHolder(beanDefinition, id); } /** * TODO until SPR-11710 will be resolved. * Captures the meta-annotation attribute values, in order. * @param importingClassMetadata The importing class metadata * @return The captured values. */ private List<MultiValueMap<String, Object>> captureMetaAnnotationValues(AnnotationMetadata importingClassMetadata) { Set<String> directAnnotations = importingClassMetadata.getAnnotationTypes(); List<MultiValueMap<String, Object>> valuesHierarchy = new ArrayList<MultiValueMap<String, Object>>(); // Need to grab the values now; see SPR-11710 for (String ann : directAnnotations) { Set<String> chain = importingClassMetadata.getMetaAnnotationTypes(ann); if (chain.contains(MessagingGateway.class.getName())) { for (String meta : chain) { valuesHierarchy.add(importingClassMetadata.getAllAnnotationAttributes(meta)); } } } return valuesHierarchy; } /** * TODO until SPR-11709 will be resolved. * For any empty values, traverses back up the meta-annotation hierarchy to * see if a value has been overridden to empty, and replaces the first such value found. * @param valuesHierarchy The values hierarchy in order. * @param annotationAttributes The current attribute values. */ private void replaceEmptyOverrides(List<MultiValueMap<String, Object>> valuesHierarchy, Map<String, Object> annotationAttributes) { for (Entry<String, Object> entry : annotationAttributes.entrySet()) { Object value = entry.getValue(); if (!MessagingAnnotationUtils.hasValue(value)) { // see if we overrode a value that was higher in the annotation chain for (MultiValueMap<String, Object> metaAttributesMap : valuesHierarchy) { Object newValue = metaAttributesMap.getFirst(entry.getKey()); if (MessagingAnnotationUtils.hasValue(newValue)) { annotationAttributes.put(entry.getKey(), newValue); break; } } } } } }