diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingController.java b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingController.java
index 9f27dbe4f0..4dafe5a78c 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingController.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingController.java
@@ -16,39 +16,49 @@
package org.springframework.integration.http;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.context.MessageSource;
+import org.springframework.validation.Errors;
+import org.springframework.validation.MapBindingResult;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
- * Inbound HTTP endpoint that implements Spring's {@link Controller}
- * interface to be used with a DispatcherServlet front controller.
+ * Inbound HTTP endpoint that implements Spring's {@link Controller} interface to be used with a DispatcherServlet front
+ * controller.
*
- * The {@link #setViewName(String) viewName} will be passed into the
- * ModelAndView return value.
+ * The {@link #setViewName(String) viewName} will be passed into the ModelAndView return value.
*
- * This endpoint will have request/reply behavior by default. That
- * can be overridden by passing false to the constructor.
- * In the request/reply case, the model map will be passed to the view, and it
- * will contain either the reply Message or payload depending on the value of
- * {@link #extractReplyPayload} (true by default, meaning just the payload).
- * The corresponding key in the map is determined by the {@link #replyKey}
- * property (with a default of "reply").
+ * This endpoint will have request/reply behavior by default. That can be overridden by passing false to
+ * the constructor. In the request/reply case, the model map will be passed to the view, and it will contain either the
+ * reply Message or payload depending on the value of {@link #extractReplyPayload} (true by default, meaning just the
+ * payload). The corresponding key in the map is determined by the {@link #replyKey} property (with a default of
+ * "reply").
*
* @author Mark Fisher
* @since 2.0
*/
public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSupport implements Controller {
+ private static final String DEFAULT_ERROR_CODE = "spring.integration.http.handler.error";
+
private static final String DEFAULT_REPLY_KEY = "reply";
+ private static final String DEFAULT_ERRORS_KEY = "errors";
private volatile String viewName;
private volatile String replyKey = DEFAULT_REPLY_KEY;
+ private volatile String errorsKey = DEFAULT_ERRORS_KEY;
+
+ private volatile String errorCode = DEFAULT_ERROR_CODE;
public HttpRequestHandlingController() {
this(true);
@@ -58,7 +68,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu
super(expectReply);
}
-
/**
* Specify the view name.
*/
@@ -67,30 +76,60 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu
}
/**
- * Specify the key to be used when adding the reply Message or payload
- * to the model map (will be payload only unless the value of
- * {@link HttpRequestHandlingController#setExtractReplyPayload(boolean)} is false).
- * The default key is "reply".
+ * Specify the key to be used when adding the reply Message or payload to the model map (will be payload only unless
+ * the value of {@link HttpRequestHandlingController#setExtractReplyPayload(boolean)} is false). The
+ * default key is "reply".
*/
public void setReplyKey(String replyKey) {
this.replyKey = (replyKey != null) ? replyKey : DEFAULT_REPLY_KEY;
}
/**
- * 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.
+ * The key used to expose {@link Errors} in the model, in the case that message handling fails. Defaults to
+ * "errors".
+ * @param errorsKey the key value to set
*/
- public final ModelAndView handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws Exception {
+ public void setErrorsKey(String errorsKey) {
+ this.errorsKey = errorsKey;
+ }
+
+ /**
+ * The error code to use to signal an error in the message handling. In the case of an error this code will be
+ * provided in an object error to be optionally translated in the standard MVC way using a {@link MessageSource}.
+ * The default value is spring.integration.http.handler.error. Three arguments are provided: the
+ * exception, its message and its stack trace as a String.
+ *
+ * @param errorCode the error code to set
+ */
+ public void setErrorCode(String errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * 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.
+ */
+ public final ModelAndView handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+ throws Exception {
ModelAndView modelAndView = new ModelAndView();
if (this.viewName != null) {
modelAndView.setViewName(this.viewName);
}
- Object reply = super.doHandleRequest(servletRequest, servletResponse);
- if (reply != null) {
- modelAndView.addObject(this.replyKey, reply);
+ try {
+ Object reply = super.doHandleRequest(servletRequest, servletResponse);
+ if (reply != null) {
+ modelAndView.addObject(this.replyKey, reply);
+ }
+ }
+ catch (Exception e) {
+ MapBindingResult errors = new MapBindingResult(new HashMap(), "dummy");
+ PrintWriter stackTrace = new PrintWriter(new StringWriter());
+ e.printStackTrace(stackTrace);
+ errors.reject(errorCode, new Object[] { e, e.getMessage(), stackTrace.toString() },
+ "A Spring Integration handler raised an exception while handling an HTTP request. The exception is of type "
+ + e.getClass() + " and it has a message: (" + e.getMessage() + ")");
+ modelAndView.addObject(errorsKey, errors);
}
return modelAndView;
}
-
}
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingEndpointSupport.java b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingEndpointSupport.java
index 2857dbddec..e18b765bdf 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingEndpointSupport.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingEndpointSupport.java
@@ -55,44 +55,38 @@ import org.springframework.web.servlet.DispatcherServlet;
/**
* Base class for HTTP request handling endpoints.
*
- * By default GET and POST requests are accepted, but the 'supportedMethods'
- * property may be set to include others or limit the options (e.g. POST only).
- * 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)}.
+ * By default GET and POST requests are accepted, but the 'supportedMethods' property may be set to include others or
+ * limit the options (e.g. POST only). 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)}.
*
- * To customize the mapping of request headers to the MessageHeaders, provide
- * a reference to a {@link HeaderMapper HeaderMapper} implementation
- * to the {@link #setHeaderMapper(HeaderMapper)} method.
+ * To customize the mapping of request headers to the MessageHeaders, provide a reference to a {@link HeaderMapper
+ * HeaderMapper} implementation to the {@link #setHeaderMapper(HeaderMapper)} method.
*
- * The behavior is "request/reply" by default. Pass 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.integration.core.MessageChannel) request channel},
- * a response will be generated. Subclasses determine how that response is
- * generated (e.g. simple status response or rendering a View).
+ * The behavior is "request/reply" by default. Pass 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.integration.MessageChannel) request channel}, a response will be
+ * generated. Subclasses determine how that response is generated (e.g. simple status response or rendering a View).
*
- * 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 false.
+ * 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
+ * false.
*
* @author Mark Fisher
* @since 2.0
*/
abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGateway {
- private static final boolean jaxb2Present =
- ClassUtils.isPresent("javax.xml.bind.Binder", HttpRequestHandlingEndpointSupport.class.getClassLoader());
+ private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder",
+ HttpRequestHandlingEndpointSupport.class.getClassLoader());
- private static final boolean jacksonPresent =
- ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", HttpRequestHandlingEndpointSupport.class.getClassLoader()) &&
- ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", HttpRequestHandlingEndpointSupport.class.getClassLoader());
-
- private static boolean romePresent =
- ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", HttpRequestHandlingEndpointSupport.class.getClassLoader());
+ private static final boolean jacksonPresent = ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper",
+ HttpRequestHandlingEndpointSupport.class.getClassLoader())
+ && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", HttpRequestHandlingEndpointSupport.class
+ .getClassLoader());
+ private static boolean romePresent = ClassUtils.isPresent("com.sun.syndication.feed.WireFeed",
+ HttpRequestHandlingEndpointSupport.class.getClassLoader());
private volatile List supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST);
@@ -108,7 +102,6 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
private volatile MultipartResolver multipartResolver;
-
public HttpRequestHandlingEndpointSupport() {
this(true);
}
@@ -130,11 +123,17 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
if (romePresent) {
// TODO add deps for:
- //this.messageConverters.add(new AtomFeedHttpMessageConverter());
- //this.messageConverters.add(new RssChannelHttpMessageConverter());
+ // this.messageConverters.add(new AtomFeedHttpMessageConverter());
+ // this.messageConverters.add(new RssChannelHttpMessageConverter());
}
}
-
+
+ /**
+ * @return whether to expect reply
+ */
+ protected boolean isExpectReply() {
+ return expectReply;
+ }
/**
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and
@@ -158,8 +157,7 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
/**
- * Specify the supported request method names for this gateway.
- * By default, only GET and POST are supported.
+ * Specify the supported request method names for this gateway. By default, only GET and POST are supported.
*/
public void setSupportedMethodNames(String... supportedMethods) {
Assert.notEmpty(supportedMethods, "at least one supported method is required");
@@ -171,8 +169,7 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
/**
- * Specify the supported request methods for this gateway.
- * By default, only GET and POST are supported.
+ * Specify the supported request methods for this gateway. By default, only GET and POST are supported.
*/
public void setSupportedMethods(HttpMethod... supportedMethods) {
Assert.notEmpty(supportedMethods, "at least one supported method is required");
@@ -180,29 +177,26 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
/**
- * 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 byte[].class.
+ * 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 byte[].class.
*/
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'.
+ * 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'.
*/
public void setExtractReplyPayload(boolean extractReplyPayload) {
- this.extractReplyPayload = 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.
+ * 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.
*/
public void setMultipartResolver(MultipartResolver multipartResolver) {
this.multipartResolver = multipartResolver;
@@ -210,12 +204,12 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
@Override
public String getComponentType() {
- return (this.expectReply) ? "http:inbound-gateway" : "http:inbound-channel-adapter";
+ 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.
+ * Locates the {@link MultipartResolver} bean based on the default name defined by the
+ * {@link DispatcherServlet#MULTIPART_RESOLVER_BEAN_NAME} constant if available.
*/
@Override
protected void onInit() throws Exception {
@@ -223,8 +217,8 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
BeanFactory beanFactory = this.getBeanFactory();
if (this.multipartResolver == null && beanFactory != null) {
try {
- MultipartResolver multipartResolver =
- this.getBeanFactory().getBean(DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
+ MultipartResolver multipartResolver = this.getBeanFactory().getBean(
+ DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + multipartResolver + "]");
}
@@ -232,19 +226,20 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
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.");
+ logger.debug("Unable to locate MultipartResolver with name '"
+ + DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME
+ + "': no multipart request handling will be supported.");
}
}
}
}
/**
- * 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.
+ * 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.
*/
- protected final Object doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException {
+ protected final Object doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+ throws IOException {
try {
ServletServerHttpRequest request = this.prepareRequest(servletRequest);
if (!this.supportedMethods.contains(request.getMethod())) {
@@ -259,12 +254,12 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
payload = this.convertParameterMap(servletRequest.getParameterMap());
}
Map headers = this.headerMapper.toHeaders(request.getHeaders());
- Message> message = MessageBuilder.withPayload(payload)
- .copyHeaders(headers)
- .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> message = MessageBuilder.withPayload(payload).copyHeaders(headers).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();
Object reply = null;
if (this.expectReply) {
reply = this.sendAndReceiveMessage(message);
@@ -287,9 +282,9 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
/**
- * 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.
+ * 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 request current HTTP request
* @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart
@@ -305,8 +300,7 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
}
/**
- * Checks if the request has a readable body (not a GET, HEAD, or OPTIONS request)
- * and a Content-Type header.
+ * Checks if the request has a readable body (not a GET, HEAD, or OPTIONS request) and a Content-Type header.
*/
private boolean isReadable(ServletServerHttpRequest request) {
HttpMethod method = request.getMethod();
@@ -354,8 +348,9 @@ abstract class HttpRequestHandlingEndpointSupport extends AbstractMessagingGatew
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 + "]");
+ throw new MessagingException(
+ "Could not convert request: no suitable HttpMessageConverter found for expected type ["
+ + expectedType.getName() + "] and content type [" + contentType + "]");
}
}
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingMessagingGateway.java b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingMessagingGateway.java
index 8dd94f67ee..efcdbc2f57 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingMessagingGateway.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/HttpRequestHandlingMessagingGateway.java
@@ -32,31 +32,31 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpRequestHandler;
/**
- * Inbound Messaging Gateway that handles HTTP Requests. May be configured as a bean in the
- * Application Context and delegated to from a simple HttpRequestHandlerServlet in
- * web.xml where the servlet and bean both have the same name. If the
- * {@link #expectReply} property is set to true, a response can generated from a
- * reply Message. Otherwise, the gateway will play the role of a unidirectional
- * Channel Adapter with a simple status-based response (e.g. 200 OK).
+ * Inbound Messaging Gateway that handles HTTP Requests. May be configured as a bean in the Application Context and
+ * delegated to from a simple HttpRequestHandlerServlet in web.xml where the servlet and bean both have the
+ * same name. If the {@link #expectReply} property is set to true, a response can generated from a reply Message.
+ * Otherwise, the gateway will play the role of a unidirectional Channel Adapter with a simple status-based response
+ * (e.g. 200 OK).
*
- * The default supported request methods are GET and POST, but the list of values can
- * be configured with the {@link #supportedMethods} property. The payload generated from
- * a GET request (or HEAD or OPTIONS if supported) will be a {@link MultiValueMap}
- * containing the parameter values. For a request containing a body (e.g. a POST),
- * the type of the payload is determined by the {@link #setRequestPayloadType(Class) request payload type}.
+ * The default supported request methods are GET and POST, but the list of values can be configured with the
+ * {@link #supportedMethods} property. The payload generated from a GET request (or HEAD or OPTIONS if supported) will
+ * be a {@link MultiValueMap} containing the parameter values. For a request containing a body (e.g. a POST), the type
+ * of the payload is determined by the {@link #setRequestPayloadType(Class) request payload type}.
*
- * If the HTTP request is a multipart and a "multipartResolver" bean has been defined
- * in the context, then it will be converted by the {@link MultipartAwareFormHttpMessageConverter}
- * as long as the default message converters have not been overwritten (although
- * providing a customized instance of the Multipart-aware converter is also an option).
+ * If the HTTP request is a multipart and a "multipartResolver" bean has been defined in the context, then it will be
+ * converted by the {@link MultipartAwareFormHttpMessageConverter} as long as the default message converters have not
+ * been overwritten (although providing a customized instance of the Multipart-aware converter is also an option).
*
- * By default a number of {@link HttpMessageConverter}s are already configured. The list
- * can be overridden by calling the {@link #setMessageConverters(List)} method.
+ * By default a number of {@link HttpMessageConverter}s are already configured. The list can be overridden by calling
+ * the {@link #setMessageConverters(List)} method.
*
* @author Mark Fisher
* @since 2.0
*/
-public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport implements HttpRequestHandler {
+public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport implements
+ HttpRequestHandler {
+
+ private volatile boolean convertExceptions;
public HttpRequestHandlingMessagingGateway() {
this(true);
@@ -66,14 +66,31 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp
super(expectReply);
}
+ /**
+ * Flag to determine if conversion and writing out of message handling exceptions should be attempted (default
+ * false, in which case they will simply be re-thrown). If the flag is true and no message converter can convert the
+ * exception a new exception will be thrown.
+ *
+ * @param convertExceptions the flag to set
+ */
+ public void setConvertExceptions(boolean convertExceptions) {
+ this.convertExceptions = convertExceptions;
+ }
/**
- * 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. That response will be written by the {@link HttpMessageConverter}s.
+ * 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. That
+ * response will be written by the {@link HttpMessageConverter}s.
*/
- public final void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
- Object responseContent = super.doHandleRequest(servletRequest, servletResponse);
+ public final void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+ throws ServletException, IOException {
+ Object responseContent = null;
+ try {
+ responseContent = super.doHandleRequest(servletRequest, servletResponse);
+ }
+ catch (Exception e) {
+ responseContent = handleExceptionInternal(e);
+ }
if (responseContent != null) {
ServletServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
@@ -81,8 +98,23 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp
}
}
+ private Object handleExceptionInternal(Exception e) throws IOException {
+ if (convertExceptions && isExpectReply()) {
+ return e;
+ }
+ else {
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ }
+ else {
+ throw (RuntimeException) e;
+ }
+ }
+ }
+
@SuppressWarnings("unchecked")
- private void writeResponse(Object content, ServletServerHttpResponse response, List acceptTypes) throws IOException {
+ private void writeResponse(Object content, ServletServerHttpResponse response, List acceptTypes)
+ throws IOException {
for (HttpMessageConverter converter : this.getMessageConverters()) {
for (MediaType acceptType : acceptTypes) {
if (converter.canWrite(content.getClass(), acceptType)) {
@@ -91,8 +123,8 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp
}
}
}
- throw new MessagingException("Could not convert reply: no suitable HttpMessageConverter found for type [" +
- content.getClass().getName() + "] and accept types [" + acceptTypes + "]");
+ throw new MessagingException("Could not convert reply: no suitable HttpMessageConverter found for type ["
+ + content.getClass().getName() + "] and accept types [" + acceptTypes + "]");
}
}
diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
index 51a30cdb32..954ba30691 100644
--- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
+++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpInboundEndpointParser.java
@@ -80,6 +80,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
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(
@@ -88,6 +89,8 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "supported-methods", "supportedMethodNames");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-payload-type");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "view-name");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "errors-key");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "error-code");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converters");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "header-mapper");
}
diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd
index 6e4f9cac29..201555339b 100644
--- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd
+++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd
@@ -1,14 +1,11 @@
-
+
-
-
+
+
- Defines an inbound HTTP-based Channel Adapter.
+ Defines an inbound HTTP-based Channel Adapter.
-
-
+
+
-
+
@@ -41,8 +38,8 @@
-
-
+
+
@@ -50,6 +47,28 @@
+
+
+
+ In the case that a view-name is specified this attribute can be used to
+ override the default key of the Errors (if the request cannot be handled).
+ Defaults to "errors" (similar to normal MVC
+ usage).
+
+
+
+
+
+
+ In the case that a view-name is specified this attribute can be used to
+ override the default error code under which the handling exception is exposed.
+ Defaults to
+ "spring.integration.http.handler.error" and is supplied with 3
+ parameters: the exception itself, its message and
+ its stack trace as a String.
+
+
+
@@ -61,7 +80,7 @@
-
+
@@ -73,14 +92,14 @@
- Defines an inbound HTTP-based Messaging Gateway.
+ Defines an inbound HTTP-based Messaging Gateway.
-
-
-
+
+
+
@@ -88,6 +107,35 @@
+
+
+
+ In the case that a view-name is specified this attribute can be used to
+ override the default key of the Errors (if the request cannot be handled).
+ Defaults to "errors" (similar to normal MVC usage).
+
+
+
+
+
+
+ In the case that a view-name is specified this attribute can be used to
+ override the default error code under which the handling exception is exposed.
+ Defaults to "spring.integration.http.handler.error" and is supplied with 3
+ parameters: the exception itself, its message and its stack trace as a String.
+
+
+
+
+
+
+ In the case that a view-name is not specified this attribute can be used to
+ override the default behaviour when there is a message handling exception (which
+ is to rethrow). If this flag is true then the normal conversion process will be
+ applied to the exception and written out to the response body.
+
+
+
@@ -106,13 +154,13 @@
-
+
-
-
+
+
@@ -122,13 +170,13 @@
- Defines an outbound HTTP-based Channel Adapter.
+ Defines an outbound HTTP-based Channel Adapter.
-
+
-
+
@@ -140,7 +188,7 @@
- The HTTP method to use when executing requests with this adapter.
+ The HTTP method to use when executing requests with this adapter.
@@ -151,21 +199,21 @@
-
+
-
-
+
+
- The expected type to which the response body should be converted.
+ The expected type to which the response body should be converted.
-
+
@@ -173,8 +221,8 @@
- Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
- all of the default converters that would normally be present on the underlying RestTemplate.
+ Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
+ all of the default converters that would normally be present on the underlying RestTemplate.
@@ -182,7 +230,7 @@
-
+
@@ -199,7 +247,7 @@
-
+
@@ -214,7 +262,7 @@
- Specifies whether this adapter should start automatically.
+ Specifies whether this adapter should start automatically.
@@ -225,25 +273,25 @@
- Defines an outbound HTTP-based Messaging Gateway.
+ Defines an outbound HTTP-based Messaging Gateway.
-
+
- URL to be used as a fallback for any request Message does not contain the request URL Message header.
+ URL to be used as a fallback for any request Message does not contain the request URL Message header.
- The HTTP method to use when executing requests with this adapter.
+ The HTTP method to use when executing requests with this adapter.
@@ -253,8 +301,8 @@
- Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
- all of the default converters that would normally be present on the underlying RestTemplate.
+ Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
+ all of the default converters that would normally be present on the underlying RestTemplate.
@@ -262,7 +310,7 @@
-
+
@@ -283,25 +331,25 @@
]]>
-
+
- The expected type to which the response body should be converted.
+ The expected type to which the response body should be converted.
-
+
-
+
-
+
@@ -313,7 +361,7 @@
]]>
-
+
@@ -359,15 +407,15 @@
- Defines common configuration for gateway adapters.
+ Defines common configuration for gateway adapters.
-
+
-
+
@@ -376,12 +424,12 @@
-
+
-
+
\ No newline at end of file
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingControllerTests.java
index 4f80db507d..ddd27e0ec0 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingControllerTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingControllerTests.java
@@ -22,12 +22,13 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
-
import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.ModelAndView;
/**
@@ -136,4 +137,29 @@ public class HttpRequestHandlingControllerTests {
assertEquals("ABC", ((Message>) reply).getPayload());
}
+ @Test
+ public void testSendWithError() throws Exception {
+ QueueChannel requestChannel = new QueueChannel() {
+ @Override
+ protected boolean doSend(Message> message, long timeout) {
+ throw new RuntimeException("Planned");
+ }
+ };
+ HttpRequestHandlingController controller = new HttpRequestHandlingController(false);
+ controller.setRequestChannel(requestChannel);
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setContent("hello".getBytes());
+ request.setContentType("text/plain");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ ModelAndView modelAndView = controller.handleRequest(request, response);
+ assertEquals(1, modelAndView.getModel().size());
+ Errors errors = (Errors) modelAndView.getModel().get("errors");
+ assertEquals(1, errors.getErrorCount());
+ ObjectError error = errors.getAllErrors().get(0);
+ assertEquals(3, error.getArguments().length);
+ assertTrue("Wrong message: "+error, ((String)error.getArguments()[1]).startsWith("failed to send Message"));
+ }
+
+
}
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingMessagingGatewayTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingMessagingGatewayTests.java
index 0ffcf6cfe2..660f79df27 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingMessagingGatewayTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpRequestHandlingMessagingGatewayTests.java
@@ -19,8 +19,18 @@ package org.springframework.integration.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import org.junit.Test;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import org.junit.Test;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -92,4 +102,50 @@ public class HttpRequestHandlingMessagingGatewayTests {
assertEquals("HELLO", response.getContentAsString());
}
+ @Test
+ public void testExceptionConversion() throws Exception {
+ QueueChannel requestChannel = new QueueChannel() {
+ @Override
+ protected boolean doSend(Message> message, long timeout) {
+ throw new RuntimeException("Planned");
+ }
+ };
+ HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
+ gateway.setRequestChannel(requestChannel);
+ gateway.setConvertExceptions(true);
+ gateway.setMessageConverters(Arrays.>asList(new DumbHttpMessageConverter()));
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader("Accept", "application/x-java-serialized-object");
+ request.setMethod("GET");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ gateway.handleRequest(request, response);
+ String content = response.getContentAsString();
+ assertEquals("Planned", content);
+ }
+
+ private static class DumbHttpMessageConverter extends AbstractHttpMessageConverter {
+
+ public DumbHttpMessageConverter() {
+ setSupportedMediaTypes(Arrays.asList(MediaType.ALL));
+ }
+
+ @Override
+ protected Exception readInternal(Class extends Exception> clazz, HttpInputMessage inputMessage) throws IOException,
+ HttpMessageNotReadableException {
+ return null;
+ }
+
+ @Override
+ protected boolean supports(Class> clazz) {
+ return true;
+ }
+
+ @Override
+ protected void writeInternal(Exception t, HttpOutputMessage outputMessage) throws IOException,
+ HttpMessageNotWritableException {
+ new PrintWriter(outputMessage.getBody()).append(t.getCause().getMessage()).flush();
+ }
+
+ }
+
}
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml
index f07aaa81aa..3fb2abab9e 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests-context.xml
@@ -20,4 +20,6 @@
+
+
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java
index 79eb81e8a3..a53a318d58 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java
@@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.integration.Message;
import org.springframework.integration.core.PollableChannel;
+import org.springframework.integration.http.HttpRequestHandlingController;
import org.springframework.integration.http.HttpRequestHandlingMessagingGateway;
import org.springframework.integration.http.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -62,6 +63,9 @@ public class HttpInboundChannelAdapterParserTests {
@Autowired
private HttpRequestHandlingMessagingGateway putOrDeleteAdapter;
+ @Autowired
+ private HttpRequestHandlingController inboundController;
+
@Test
@SuppressWarnings("unchecked")
@@ -137,6 +141,13 @@ public class HttpInboundChannelAdapterParserTests {
assertTrue(supportedMethods.contains(HttpMethod.DELETE));
}
+ @Test
+ public void testController() throws Exception {
+ DirectFieldAccessor accessor = new DirectFieldAccessor(inboundController);
+ String errorCode = (String) accessor.getPropertyValue("errorCode");
+ assertEquals("oops", errorCode);
+ }
+
@SuppressWarnings("serial")
private static class TestObject implements Serializable {
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml
index f98dc5e77d..6303b7a348 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests-context.xml
@@ -17,6 +17,8 @@
-
+
+
+
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java
index ead8ea842c..2381618c0b 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundGatewayParserTests.java
@@ -18,6 +18,7 @@ package org.springframework.integration.http.config;
import static org.hamcrest.CoreMatchers.any;
import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
@@ -28,10 +29,12 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.core.SubscribableChannel;
+import org.springframework.integration.http.HttpRequestHandlingController;
import org.springframework.integration.http.HttpRequestHandlingMessagingGateway;
import org.springframework.integration.http.MockHttpServletRequest;
import org.springframework.integration.http.MockHttpServletResponse;
@@ -49,6 +52,9 @@ public class HttpInboundGatewayParserTests {
@Autowired
private HttpRequestHandlingMessagingGateway gateway;
+ @Autowired
+ private HttpRequestHandlingController inboundController;
+
@Autowired
private SubscribableChannel requests;
@@ -60,6 +66,7 @@ public class HttpInboundGatewayParserTests {
public void checkConfig() {
assertNotNull(gateway);
assertThat((Boolean) getPropertyValue(gateway, "expectReply"), is(true));
+ assertThat((Boolean) getPropertyValue(gateway, "convertExceptions"), is(true));
assertThat((PollableChannel) getPropertyValue(gateway, "replyChannel"), is(responses));
}
@@ -76,4 +83,11 @@ public class HttpInboundGatewayParserTests {
assertThat(response.getContentType(), is("application/x-java-serialized-object"));
}
+ @Test
+ public void testController() throws Exception {
+ DirectFieldAccessor accessor = new DirectFieldAccessor(inboundController);
+ String errorCode = (String) accessor.getPropertyValue("errorCode");
+ assertEquals("oops", errorCode);
+ }
+
}