/* * Copyright 2013-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.inbound; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.HttpRequestHandler; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.mvc.condition.NameValueExpression; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * The {@link org.springframework.web.servlet.HandlerMapping} implementation that * detects and registers {@link RequestMappingInfo}s for * {@link HttpRequestHandlingEndpointSupport} from a Spring Integration HTTP configuration * of {@code <inbound-channel-adapter/>} and {@code <inbound-gateway/>} elements. * <p> * This class is automatically configured as a bean in the application context during the * parsing phase of the {@code <inbound-channel-adapter/>} and {@code <inbound-gateway/>} * elements, if there is none registered, yet. However it can be configured as a regular * bean with appropriate configuration for {@link RequestMappingHandlerMapping}. * It is recommended to have only one similar bean in the application context using the 'id' * {@link org.springframework.integration.http.support.HttpContextUtils#HANDLER_MAPPING_BEAN_NAME}. * <p> * In most cases, Spring MVC offers to configure Request Mapping via * {@code org.springframework.stereotype.Controller} and * {@link org.springframework.web.bind.annotation.RequestMapping}. * That's why Spring MVC's Handler Mapping infrastructure relies on * {@link org.springframework.web.method.HandlerMethod}, as different methods at the same * {@code org.springframework.stereotype.Controller} user-class may have their own * {@link org.springframework.web.bind.annotation.RequestMapping}. * On the other side, all Spring Integration HTTP Inbound Endpoints are configured on * the basis of the same {@link HttpRequestHandlingEndpointSupport} class and there is no * single {@link RequestMappingInfo} configuration without * {@link org.springframework.web.method.HandlerMethod} in Spring MVC. * Accordingly {@link IntegrationRequestMappingHandlerMapping} is a * {@link org.springframework.web.servlet.HandlerMapping} * compromise implementation between method-level annotations and component-level * (e.g. Spring Integration XML) configurations. * * @author Artem Bilan * @since 3.0 * @see RequestMapping * @see RequestMappingHandlerMapping */ public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping implements ApplicationListener<ContextRefreshedEvent> { private static final Method HANDLE_REQUEST_METHOD = ReflectionUtils.findMethod(HttpRequestHandler.class, "handleRequest", HttpServletRequest.class, HttpServletResponse.class); private final AtomicBoolean initialized = new AtomicBoolean(); @Override protected boolean isHandler(Class<?> beanType) { return HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanType); } @Override protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Object bean = handlerMethod.getBean(); if (bean instanceof HttpRequestHandlingEndpointSupport) { handler = bean; } } return super.getHandlerExecutionChain(handler, request); } @Override protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) { if (handler instanceof HandlerMethod) { return super.getCorsConfiguration(handler, request); } else { return super.getCorsConfiguration(new HandlerMethod(handler, HANDLE_REQUEST_METHOD), request); } } @Override protected void detectHandlerMethods(Object handler) { if (handler instanceof String) { handler = this.getApplicationContext().getBean((String) handler); } RequestMappingInfo mapping = this.getMappingForEndpoint((HttpRequestHandlingEndpointSupport) handler); if (mapping != null) { registerMapping(mapping, handler, HANDLE_REQUEST_METHOD); } } @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { CrossOrigin crossOrigin = ((HttpRequestHandlingEndpointSupport) handler).getCrossOrigin(); if (crossOrigin != null) { CorsConfiguration config = new CorsConfiguration(); for (String origin : crossOrigin.getOrigin()) { config.addAllowedOrigin(origin); } for (RequestMethod requestMethod : crossOrigin.getMethod()) { config.addAllowedMethod(requestMethod.name()); } for (String header : crossOrigin.getAllowedHeaders()) { config.addAllowedHeader(header); } for (String header : crossOrigin.getExposedHeaders()) { config.addExposedHeader(header); } if (crossOrigin.getAllowCredentials() != null) { config.setAllowCredentials(crossOrigin.getAllowCredentials()); } if (crossOrigin.getMaxAge() != -1) { config.setMaxAge(crossOrigin.getMaxAge()); } if (CollectionUtils.isEmpty(config.getAllowedMethods())) { for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) { config.addAllowedMethod(allowedMethod.name()); } } if (CollectionUtils.isEmpty(config.getAllowedHeaders())) { for (NameValueExpression<String> headerExpression : mappingInfo.getHeadersCondition().getExpressions()) { if (!headerExpression.isNegated()) { config.addAllowedHeader(headerExpression.getName()); } } } return config; } return null; } /** * Created a {@link RequestMappingInfo} from a * 'Spring Integration HTTP Inbound Endpoint' {@link RequestMapping}. * @see RequestMappingHandlerMapping#getMappingForMethod */ private RequestMappingInfo getMappingForEndpoint(HttpRequestHandlingEndpointSupport endpoint) { final RequestMapping requestMapping = endpoint.getRequestMapping(); if (ObjectUtils.isEmpty(requestMapping.getPathPatterns())) { return null; } Map<String, Object> requestMappingAttributes = new HashMap<String, Object>(); requestMappingAttributes.put("name", endpoint.getComponentName()); requestMappingAttributes.put("value", requestMapping.getPathPatterns()); requestMappingAttributes.put("path", requestMapping.getPathPatterns()); requestMappingAttributes.put("method", requestMapping.getRequestMethods()); requestMappingAttributes.put("params", requestMapping.getParams()); requestMappingAttributes.put("headers", requestMapping.getHeaders()); requestMappingAttributes.put("consumes", requestMapping.getConsumes()); requestMappingAttributes.put("produces", requestMapping.getProduces()); org.springframework.web.bind.annotation.RequestMapping requestMappingAnnotation = AnnotationUtils.synthesizeAnnotation(requestMappingAttributes, org.springframework.web.bind.annotation.RequestMapping.class, null); return createRequestMappingInfo(requestMappingAnnotation, getCustomTypeCondition(endpoint.getClass())); } @Override public void afterPropertiesSet() { // No-op in favor of onApplicationEvent } /** * {@link HttpRequestHandlingEndpointSupport}s may depend on auto-created * {@code requestChannel}s, so MVC Handlers detection should be postponed * as late as possible. * @see RequestMappingHandlerMapping#afterPropertiesSet() */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (!this.initialized.getAndSet(true)) { super.afterPropertiesSet(); } } }