/* * Copyright 2002-2016 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.http.config; import java.util.List; import org.w3c.dom.Element; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; 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.ManagedMap; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.http.inbound.CrossOrigin; import org.springframework.integration.http.inbound.HttpRequestHandlingController; import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway; import org.springframework.integration.http.inbound.RequestMapping; import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; /** * Parser for the 'inbound-channel-adapter' and 'inbound-gateway' elements * of the 'http' namespace. The constructor's boolean value specifies whether * a reply is to be expected. This value should be 'false' for the * 'inbound-channel-adapter' and 'true' for the 'inbound-gateway'. * * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell * @author Biju Kunjummen * @author Artem Bilan */ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParser { private final boolean expectReply; public HttpInboundEndpointParser(boolean expectReply) { this.expectReply = expectReply; } @Override protected String getBeanClassName(Element element) { return element.hasAttribute("view-name") || element.hasAttribute("view-expression") ? HttpRequestHandlingController.class.getName() : HttpRequestHandlingMessagingGateway.class.getName(); } @Override protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { String id = super.resolveId(element, definition, parserContext); if (!this.expectReply && !element.hasAttribute("channel")) { // the created channel will get the 'id', so the adapter's bean name includes a suffix id = id + ".adapter"; } if (!StringUtils.hasText(id)) { id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); } return id; } @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { builder.addConstructorArgValue(this.expectReply); String inputChannelAttributeName = this.getInputChannelAttributeName(); String inputChannelRef = element.getAttribute(inputChannelAttributeName); if (!StringUtils.hasText(inputChannelRef)) { if (this.expectReply) { parserContext.getReaderContext().error( "a '" + inputChannelAttributeName + "' reference is required", element); } else { inputChannelRef = IntegrationNamespaceUtils.createDirectChannel(element, parserContext); } } builder.addPropertyReference("requestChannel", inputChannelRef); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); BeanDefinition payloadExpressionDef = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("payload-expression", element); if (payloadExpressionDef != null) { builder.addPropertyValue("payloadExpression", payloadExpressionDef); } List<Element> headerElements = DomUtils.getChildElementsByTagName(element, "header"); if (!CollectionUtils.isEmpty(headerElements)) { ManagedMap<String, Object> headerElementsMap = new ManagedMap<String, Object>(); for (Element headerElement : headerElements) { String name = headerElement.getAttribute(NAME_ATTRIBUTE); BeanDefinition headerExpressionDef = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE, headerElement); if (headerExpressionDef != null) { headerElementsMap.put(name, headerExpressionDef); } } builder.addPropertyValue("headerExpressions", headerElementsMap); } if (this.expectReply) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-reply-payload"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-key"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "convert-exceptions"); } else { IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout", "requestTimeout"); } BeanDefinition expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("view-name", "view-expression", parserContext, element, false); if (expressionDef != null) { builder.addPropertyValue("viewExpression", expressionDef); } IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "errors-key"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "error-code"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converters"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "merge-with-default-converters"); String headerMapper = element.getAttribute("header-mapper"); String mappedRequestHeaders = element.getAttribute("mapped-request-headers"); String mappedResponseHeaders = element.getAttribute("mapped-response-headers"); boolean hasMappedRequestHeaders = StringUtils.hasText(mappedRequestHeaders); boolean hasMappedResponseHeaders = StringUtils.hasText(mappedResponseHeaders); if (StringUtils.hasText(headerMapper)) { if (hasMappedRequestHeaders || hasMappedResponseHeaders) { parserContext.getReaderContext().error("Neither 'mapped-request-headers' or 'mapped-response-headers' " + "attributes are allowed when a 'header-mapper' has been specified.", parserContext.extractSource(element)); } builder.addPropertyReference("headerMapper", headerMapper); } else { BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class); headerMapperBuilder.setFactoryMethod("inboundMapper"); if (hasMappedRequestHeaders) { headerMapperBuilder.addPropertyValue("inboundHeaderNames", mappedRequestHeaders); } if (hasMappedResponseHeaders) { headerMapperBuilder.addPropertyValue("outboundHeaderNames", mappedResponseHeaders); } builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition()); } BeanDefinition requestMappingDef = createRequestMapping(element); builder.addPropertyValue("requestMapping", requestMappingDef); Element crossOriginElement = DomUtils.getChildElementByTagName(element, "cross-origin"); if (crossOriginElement != null) { BeanDefinitionBuilder crossOriginBuilder = BeanDefinitionBuilder.genericBeanDefinition(CrossOrigin.class); String[] attributes = {"origin", "allowed-headers", "exposed-headers", "max-age", "method"}; for (String crossOriginAttribute : attributes) { IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement, crossOriginAttribute); } IntegrationNamespaceUtils.setValueIfAttributeDefined(crossOriginBuilder, crossOriginElement, "allow-credentials", true); builder.addPropertyValue("crossOrigin", crossOriginBuilder.getBeanDefinition()); } IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type", "requestPayloadType"); BeanDefinition statusCodeExpressionDef = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("status-code-expression", element); if (statusCodeExpressionDef == null) { statusCodeExpressionDef = IntegrationNamespaceUtils .createExpressionDefIfAttributeDefined("reply-timeout-status-code-expression", element); } if (statusCodeExpressionDef != null) { builder.addPropertyValue("statusCodeExpression", statusCodeExpressionDef); } IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE); } private String getInputChannelAttributeName() { return this.expectReply ? "request-channel" : "channel"; } private BeanDefinition createRequestMapping(Element element) { BeanDefinitionBuilder requestMappingDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(RequestMapping.class); String methods = element.getAttribute("supported-methods"); if (StringUtils.hasText(methods)) { requestMappingDefBuilder.addPropertyValue("methods", methods.toUpperCase()); } IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, element, "path", "pathPatterns"); Element requestMappingElement = DomUtils.getChildElementByTagName(element, "request-mapping"); if (requestMappingElement != null) { for (String requestMappingAttribute : new String[]{"params", "headers", "consumes", "produces"}) { IntegrationNamespaceUtils.setValueIfAttributeDefined(requestMappingDefBuilder, requestMappingElement, requestMappingAttribute); } } return requestMappingDefBuilder.getRawBeanDefinition(); } }