/* * Copyright 2002-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.http.inbound; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Source; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.integration.MessageTimeoutException; import org.springframework.integration.context.OrderlyShutdownCapable; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.integration.http.converter.MultipartAwareFormHttpMessageConverter; import org.springframework.integration.http.multipart.MultipartHttpInputMessage; import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.mapping.HeaderMapper; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.json.JacksonPresent; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerMapping; /** * Base class for HTTP request handling endpoints. * <p> * By default GET and POST requests are accepted via a supplied default instance * of {@link RequestMapping}. * A GET request will generate a payload containing its 'parameterMap' while a POST * request will be converted to a Message payload according to the registered * {@link HttpMessageConverter}s. * Several are registered by default, but the list can be explicitly set via * {@link #setMessageConverters(List)}. * <p> * To customize the mapping of request headers to the MessageHeaders, provide a * reference to a {@code HeaderMapper<HttpHeaders>} implementation * to the {@link #setHeaderMapper(HeaderMapper)} method. * <p> * The behavior is "request/reply" by default. Pass {@code false} to the constructor * to force send-only as opposed to sendAndReceive. Send-only means that as soon as * the Message is created and passed to the * {@link #setRequestChannel(org.springframework.messaging.MessageChannel) request channel}, * a response will be generated. Subclasses determine how that response is generated * (e.g. simple status response or rendering a View). * <p> * In a request-reply scenario, the reply Message's payload will be extracted prior * to generating a response by default. * To have the entire serialized Message available for the response, switch the * {@link #extractReplyPayload} value to {@code false}. * * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan * @author Biju Kunjummen * @since 2.0 */ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySupport implements OrderlyShutdownCapable { private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", HttpRequestHandlingEndpointSupport.class.getClassLoader()); private static final boolean romeToolsPresent = ClassUtils.isPresent("com.rometools.rome.feed.atom.Feed", HttpRequestHandlingEndpointSupport.class.getClassLoader()); private static final List<HttpMethod> nonReadableBodyHttpMethods = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS); private final List<HttpMessageConverter<?>> defaultMessageConverters = new ArrayList<HttpMessageConverter<?>>(); private final boolean expectReply; private volatile List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>(); private volatile RequestMapping requestMapping = new RequestMapping(); private volatile CrossOrigin crossOrigin; private volatile Class<?> requestPayloadType = null; private volatile boolean convertersMerged; private volatile boolean mergeWithDefaultConverters = false; private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper(); private volatile boolean extractReplyPayload = true; private volatile MultipartResolver multipartResolver; private volatile Expression payloadExpression; private volatile Map<String, Expression> headerExpressions; private volatile Expression statusCodeExpression; private volatile EvaluationContext evaluationContext; private final AtomicInteger activeCount = new AtomicInteger(); /** * Construct a gateway that will wait for the {@link #setReplyTimeout(long) * replyTimeout} for a reply; if the timeout is exceeded a '500 Internal Server Error' * status code is returned. This can be modified using the * {@link #setStatusCodeExpression(Expression) statusCodeExpression}. * @see #setReplyTimeout(long) * @see #setStatusCodeExpression(Expression) */ public HttpRequestHandlingEndpointSupport() { this(true); } /** * Construct a gateway. If 'expectReply' is true it will wait for the * {@link #setReplyTimeout(long) replyTimeout} for a reply; if the timeout is exceeded * a '500 Internal Server Error' status code is returned. This can be modified using * the {@link #setStatusCodeExpression(Expression) statusCodeExpression}. * If 'false', a 200 OK status will be returned; this can also be modified using * {@link #setStatusCodeExpression(Expression) statusCodeExpression}. * @param expectReply true if a reply is expected from the downstream flow. * @see #setReplyTimeout(long) * @see #setStatusCodeExpression(Expression) */ public HttpRequestHandlingEndpointSupport(boolean expectReply) { super(expectReply); this.expectReply = expectReply; this.defaultMessageConverters.add(new MultipartAwareFormHttpMessageConverter()); this.defaultMessageConverters.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); this.defaultMessageConverters.add(stringHttpMessageConverter); this.defaultMessageConverters.add(new ResourceHttpMessageConverter()); SourceHttpMessageConverter<Source> sourceConverter = new SourceHttpMessageConverter<Source>(); this.defaultMessageConverters.add(sourceConverter); if (jaxb2Present) { this.defaultMessageConverters.add(new Jaxb2RootElementHttpMessageConverter()); if (logger.isDebugEnabled()) { logger.debug("'Jaxb2RootElementHttpMessageConverter' was added to the 'defaultMessageConverters'."); } } if (JacksonPresent.isJackson2Present()) { this.defaultMessageConverters.add(new MappingJackson2HttpMessageConverter()); if (logger.isDebugEnabled()) { logger.debug("'MappingJackson2HttpMessageConverter' was added to the 'defaultMessageConverters'."); } } if (romeToolsPresent) { this.defaultMessageConverters.add(new AtomFeedHttpMessageConverter()); this.defaultMessageConverters.add(new RssChannelHttpMessageConverter()); if (logger.isDebugEnabled()) { logger.debug("'AtomFeedHttpMessageConverter' was added to the 'defaultMessageConverters'."); logger.debug("'RssChannelHttpMessageConverter' was added to the 'defaultMessageConverters'."); } } } /** * @return Whether to expect a reply. */ protected boolean isExpectReply() { return this.expectReply; } /** * Specifies a SpEL expression to evaluate in order to generate the Message payload. * The EvaluationContext will be populated with an HttpEntity instance as the root object, * and it may contain variables: * <ul> * <li><code>#pathVariables</code></li> * <li><code>#requestParams</code></li> * <li><code>#requestAttributes</code></li> * <li><code>#requestHeaders</code></li> * <li><code>#matrixVariables</code></li> * <li><code>#cookies</code> * </ul> * @param payloadExpression The payload expression. */ public void setPayloadExpression(Expression payloadExpression) { this.payloadExpression = payloadExpression; } /** * Specifies a Map of SpEL expressions to evaluate in order to generate the Message headers. * The keys in the map will be used as the header names. When evaluating the expression, * the EvaluationContext will be populated with an HttpEntity instance as the root object, * and it may contain variables: * <ul> * <li><code>#pathVariables</code></li> * <li><code>#requestParams</code></li> * <li><code>#requestAttributes</code></li> * <li><code>#requestHeaders</code></li> * <li><code>#matrixVariables</code></li> * <li><code>#cookies</code> * </ul> * @param headerExpressions The header expressions. */ public void setHeaderExpressions(Map<String, Expression> headerExpressions) { this.headerExpressions = headerExpressions; } /** * Set the message body converters to use. These converters are used to convert from and to HTTP requests and * responses. * @param messageConverters The message converters. */ public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) { Assert.noNullElements(messageConverters.toArray(), "'messageConverters' must not contain null entries"); List<HttpMessageConverter<?>> localConverters = new ArrayList<HttpMessageConverter<?>>(messageConverters); if (this.mergeWithDefaultConverters) { localConverters.addAll(this.defaultMessageConverters); this.convertersMerged = true; } this.messageConverters = localConverters; } protected List<HttpMessageConverter<?>> getMessageConverters() { return this.messageConverters; } /** * Flag which determines if the default converters should be available after * custom converters. * @param mergeWithDefaultConverters true to merge, false to replace. */ public void setMergeWithDefaultConverters(boolean mergeWithDefaultConverters) { this.mergeWithDefaultConverters = mergeWithDefaultConverters; } /** * Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders. * @param headerMapper The header mapper. */ public void setHeaderMapper(HeaderMapper<HttpHeaders> headerMapper) { Assert.notNull(headerMapper, "headerMapper must not be null"); this.headerMapper = headerMapper; } /** * Set the {@link RequestMapping} which allows you to specify a flexible RESTFul-mapping for this endpoint. * @param requestMapping The request mapping. */ public void setRequestMapping(RequestMapping requestMapping) { Assert.notNull(requestMapping, "requestMapping must not be null"); this.requestMapping = requestMapping; } public final RequestMapping getRequestMapping() { return this.requestMapping; } /** * Set the {@link CrossOrigin} to permit cross origin requests for this endpoint. * @param crossOrigin the CrossOrigin config. * @since 4.2 */ public void setCrossOrigin(CrossOrigin crossOrigin) { this.crossOrigin = crossOrigin; } public CrossOrigin getCrossOrigin() { return this.crossOrigin; } /** * Specify the type of payload to be generated when the inbound HTTP request content is read by the * {@link HttpMessageConverter}s. By default this value is null which means at runtime any "text" Content-Type will * result in String while all others default to <code>byte[].class</code>. * @param requestPayloadType The payload type. */ public void setRequestPayloadType(Class<?> requestPayloadType) { this.requestPayloadType = requestPayloadType; } /** * Specify whether only the reply Message's payload should be passed in the response. If this is set to 'false', the * entire Message will be used to generate the response. The default is 'true'. * @param extractReplyPayload true to extract the reply payload. */ public void setExtractReplyPayload(boolean extractReplyPayload) { this.extractReplyPayload = extractReplyPayload; } /** * Specify the {@link MultipartResolver} to use when checking requests. If no resolver is provided, the * "multipartResolver" bean in the context will be used as a fallback. If that is not available either, this * endpoint will not support multipart requests. * @param multipartResolver The multipart resolver. */ public void setMultipartResolver(MultipartResolver multipartResolver) { this.multipartResolver = multipartResolver; } /** * Specify the {@link Expression} to resolve a status code for Response to override * the default '200 OK' or '500 Internal Server Error' for a timeout. * @param statusCodeExpression The status code Expression. * @since 5.0 * @see #setStatusCodeExpression(Expression) */ public void setStatusCodeExpressionString(String statusCodeExpression) { setStatusCodeExpression(EXPRESSION_PARSER.parseExpression(statusCodeExpression)); } /** * Specify the {@link Expression} to resolve a status code for Response to override * the default '200 OK' or '500 Internal Server Error' for a timeout. * <p>The {@link #statusCodeExpression} is applied only for the one-way * {@code <http:inbound-channel-adapter/>} or when no reply (timeout) is received for * a gateway. The {@code <http:inbound-gateway/>} (or whenever * {@link #HttpRequestHandlingEndpointSupport(boolean) expectReply} is true) resolves * an {@link HttpStatus} from the * {@link org.springframework.integration.http.HttpHeaders#STATUS_CODE} reply * {@link Message} header. * @param statusCodeExpression The status code Expression. * @since 4.1 * @see #setReplyTimeout(long) * @see HttpRequestHandlingEndpointSupport#HttpRequestHandlingEndpointSupport(boolean) */ public void setStatusCodeExpression(Expression statusCodeExpression) { this.statusCodeExpression = statusCodeExpression; } @Override public String getComponentType() { return (this.expectReply) ? "http:inbound-gateway" : "http:inbound-channel-adapter"; } /** * Locates the {@link MultipartResolver} bean based on the default name defined by the * {@link DispatcherServlet#MULTIPART_RESOLVER_BEAN_NAME} constant if available. * Sets up default converters if no converters set, or {@link #setMergeWithDefaultConverters(boolean)} * was called with true after the converters were set. */ @Override protected void onInit() throws Exception { super.onInit(); BeanFactory beanFactory = this.getBeanFactory(); if (this.multipartResolver == null && beanFactory != null) { try { MultipartResolver multipartResolver = beanFactory.getBean( DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isDebugEnabled()) { logger.debug("Using MultipartResolver [" + multipartResolver + "]"); } this.multipartResolver = multipartResolver; } catch (NoSuchBeanDefinitionException e) { if (logger.isDebugEnabled()) { logger.debug("Unable to locate MultipartResolver with name '" + DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME + "': no multipart request handling will be supported."); } } } if (this.messageConverters.size() == 0 || (this.mergeWithDefaultConverters && !this.convertersMerged)) { this.messageConverters.addAll(this.defaultMessageConverters); } this.validateSupportedMethods(); if (this.statusCodeExpression != null) { this.evaluationContext = createEvaluationContext(); } } /** * Handles the HTTP request by generating a Message and sending it to the request channel. If this gateway's * 'expectReply' property is true, it will also generate a response from the reply Message once received. * @param servletRequest The servlet request. * @param servletResponse The servlet response. * @return The response Message. * @throws IOException Any IOException. */ protected final Message<?> doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { if (isRunning()) { return actualDoHandleRequest(servletRequest, servletResponse); } else { return createServiceUnavailableResponse(); } } @SuppressWarnings({"rawtypes", "unchecked"}) private Message<?> actualDoHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { this.activeCount.incrementAndGet(); try { ServletServerHttpRequest request = this.prepareRequest(servletRequest); Object requestBody = null; if (this.isReadable(request)) { requestBody = this.extractRequestBody(request); } HttpEntity httpEntity = new HttpEntity(requestBody, request.getHeaders()); StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); evaluationContext.setVariable("requestAttributes", RequestContextHolder.currentRequestAttributes()); MultiValueMap<String, String> requestParams = this.convertParameterMap(servletRequest.getParameterMap()); evaluationContext.setVariable("requestParams", requestParams); evaluationContext.setVariable("requestHeaders", new ServletServerHttpRequest(servletRequest).getHeaders()); Cookie[] requestCookies = servletRequest.getCookies(); if (!ObjectUtils.isEmpty(requestCookies)) { Map<String, Cookie> cookies = new HashMap<String, Cookie>(requestCookies.length); for (Cookie requestCookie : requestCookies) { cookies.put(requestCookie.getName(), requestCookie); } evaluationContext.setVariable("cookies", cookies); } Map<String, String> pathVariables = (Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); if (!CollectionUtils.isEmpty(pathVariables)) { if (logger.isDebugEnabled()) { logger.debug("Mapped path variables: " + pathVariables); } evaluationContext.setVariable("pathVariables", pathVariables); } Map<String, MultiValueMap<String, String>> matrixVariables = (Map<String, MultiValueMap<String, String>>) servletRequest .getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE); if (!CollectionUtils.isEmpty(matrixVariables)) { if (logger.isDebugEnabled()) { logger.debug("Mapped matrix variables: " + matrixVariables); } evaluationContext.setVariable("matrixVariables", matrixVariables); } Map<String, Object> headers = this.headerMapper.toHeaders(request.getHeaders()); Object payload = null; if (this.payloadExpression != null) { // create payload based on SpEL payload = this.payloadExpression.getValue(evaluationContext); } if (!CollectionUtils.isEmpty(this.headerExpressions)) { for (Entry<String, Expression> entry : this.headerExpressions.entrySet()) { String headerName = entry.getKey(); Expression headerExpression = entry.getValue(); Object headerValue = headerExpression.getValue(evaluationContext); if (headerValue != null) { headers.put(headerName, headerValue); } } } if (payload == null) { if (requestBody != null) { payload = requestBody; } else { payload = requestParams; } } AbstractIntegrationMessageBuilder<?> messageBuilder = null; if (payload instanceof Message<?>) { messageBuilder = this.getMessageBuilderFactory().fromMessage((Message<?>) payload) .copyHeadersIfAbsent(headers); } else { messageBuilder = this.getMessageBuilderFactory().withPayload(payload).copyHeaders(headers); } Message<?> message = messageBuilder .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, request.getURI().toString()) .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, request.getMethod().toString()) .setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, servletRequest.getUserPrincipal()) .build(); Message<?> reply = null; if (this.expectReply) { try { reply = this.sendAndReceiveMessage(message); } catch (MessageTimeoutException e) { if (this.statusCodeExpression != null) { reply = getMessageBuilderFactory().withPayload(e.getMessage()) .setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, evaluateHttpStatus()) .build(); } else { reply = getMessageBuilderFactory().withPayload(e.getMessage()) .setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR) .build(); } } } else { this.send(message); } return reply; } finally { this.postProcessRequest(servletRequest); this.activeCount.decrementAndGet(); } } private Message<?> createServiceUnavailableResponse() { if (logger.isDebugEnabled()) { logger.debug("Endpoint is stopped; returning status " + HttpStatus.SERVICE_UNAVAILABLE); } return this.getMessageBuilderFactory().withPayload("Endpoint is stopped") .setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.SERVICE_UNAVAILABLE) .build(); } /** * Converts the reply message to the appropriate HTTP reply object and * sets up the {@link ServletServerHttpResponse}. * @param response The ServletServerHttpResponse. * @param replyMessage The reply message. * @return The message payload (if {@link #extractReplyPayload}) otherwise the message. */ protected final Object setupResponseAndConvertReply(ServletServerHttpResponse response, Message<?> replyMessage) { this.headerMapper.fromHeaders(replyMessage.getHeaders(), response.getHeaders()); HttpStatus httpStatus = this.resolveHttpStatusFromHeaders(replyMessage.getHeaders()); if (httpStatus != null) { response.setStatusCode(httpStatus); } Object reply = replyMessage; if (this.extractReplyPayload) { reply = replyMessage.getPayload(); } return reply; } protected void setStatusCodeIfNeeded(ServletServerHttpResponse response) { if (this.statusCodeExpression != null) { HttpStatus httpStatus = evaluateHttpStatus(); if (httpStatus != null) { response.setStatusCode(httpStatus); } } } private HttpStatus evaluateHttpStatus() { if (this.evaluationContext == null) { this.evaluationContext = createEvaluationContext(); } Object value = this.statusCodeExpression.getValue(this.evaluationContext); return buildHttpStatus(value); } /** * Prepares an instance of {@link ServletServerHttpRequest} from the raw * {@link HttpServletRequest}. Also converts the request into a multipart request to * make multiparts available if necessary. If no multipart resolver is set, * simply returns the existing request. * @param servletRequest current HTTP request * @return the processed request (multipart wrapper if necessary) * @see MultipartResolver#resolveMultipart */ private ServletServerHttpRequest prepareRequest(HttpServletRequest servletRequest) { if (servletRequest instanceof MultipartHttpServletRequest) { return new MultipartHttpInputMessage((MultipartHttpServletRequest) servletRequest); } if (this.multipartResolver != null && this.multipartResolver.isMultipart(servletRequest)) { return new MultipartHttpInputMessage(this.multipartResolver.resolveMultipart(servletRequest)); } return new ServletServerHttpRequest(servletRequest); } /** * Checks if the request has a readable body (not a GET, HEAD, or OPTIONS request). */ private boolean isReadable(ServletServerHttpRequest request) { return !(CollectionUtils.containsInstance(nonReadableBodyHttpMethods, request.getMethod())); } /** * Clean up any resources used by the given multipart request (if any). * @param request current HTTP request * @see MultipartResolver#cleanupMultipart */ private void postProcessRequest(HttpServletRequest request) { if (this.multipartResolver != null && request instanceof MultipartHttpServletRequest) { this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request); } } /** * Converts a servlet request's parameterMap to a {@link MultiValueMap}. */ private MultiValueMap<String, String> convertParameterMap(Map<String, String[]> parameterMap) { MultiValueMap<String, String> convertedMap = new LinkedMultiValueMap<String, String>(parameterMap.size()); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String[] values = entry.getValue(); for (String value : values) { convertedMap.add(entry.getKey(), value); } } return convertedMap; } @SuppressWarnings({"unchecked", "rawtypes"}) private Object extractRequestBody(ServletServerHttpRequest request) throws IOException { MediaType contentType = request.getHeaders().getContentType(); if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> expectedType = this.requestPayloadType; if (expectedType == null) { expectedType = ("text".equals(contentType.getType())) ? String.class : byte[].class; } for (HttpMessageConverter<?> converter : this.messageConverters) { if (converter.canRead(expectedType, contentType)) { return converter.read((Class) expectedType, request); } } throw new MessagingException( "Could not convert request: no suitable HttpMessageConverter found for expected type [" + expectedType.getName() + "] and content type [" + contentType + "]"); } private HttpStatus resolveHttpStatusFromHeaders(MessageHeaders headers) { Object httpStatusFromHeader = headers.get(org.springframework.integration.http.HttpHeaders.STATUS_CODE); return buildHttpStatus(httpStatusFromHeader); } private HttpStatus buildHttpStatus(Object httpStatusValue) { HttpStatus httpStatus = null; if (httpStatusValue instanceof HttpStatus) { httpStatus = (HttpStatus) httpStatusValue; } else if (httpStatusValue instanceof Integer) { httpStatus = HttpStatus.valueOf((Integer) httpStatusValue); } else if (httpStatusValue instanceof String) { httpStatus = HttpStatus.valueOf(Integer.parseInt((String) httpStatusValue)); } return httpStatus; } protected StandardEvaluationContext createEvaluationContext() { return ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); } private void validateSupportedMethods() { if (this.requestPayloadType != null && CollectionUtils.containsAny(nonReadableBodyHttpMethods, Arrays.asList(this.requestMapping.getMethods()))) { if (logger.isWarnEnabled()) { logger.warn("The 'requestPayloadType' attribute will have no relevance for one " + "of the specified HTTP methods '" + nonReadableBodyHttpMethods + "'"); } } } @Override public int beforeShutdown() { stop(); return this.activeCount.get(); } @Override public int afterShutdown() { return this.activeCount.get(); } }