/*
* 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.inbound;
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.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.MapBindingResult;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
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.
* <p>
* The {@link #setViewName(String) viewName} will be passed into the ModelAndView return value.
* <p>
* This endpoint will have request/reply behavior by default. That can be overridden by passing <code>false</code> to
* the constructor. In the request/reply case, the core 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
* @author Gary Russell
* @author Artem Bilan
* @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 Expression viewExpression;
private volatile StandardEvaluationContext evaluationContext;
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);
}
public HttpRequestHandlingController(boolean expectReply) {
super(expectReply);
}
/**
* Specify the view name.
*
* @param viewName The view name.
*/
public void setViewName(String viewName) {
Assert.isTrue(StringUtils.hasText(viewName), "View name must contain text");
this.viewExpression = new LiteralExpression(viewName);
}
/**
* Specify the key to be used when adding the reply Message or payload to the core map (will be payload only unless
* the value of {@link HttpRequestHandlingController#setExtractReplyPayload(boolean)} is <code>false</code>). The
* default key is "reply".
*
* @param replyKey The reply key.
*/
public void setReplyKey(String replyKey) {
this.replyKey = (replyKey != null) ? replyKey : DEFAULT_REPLY_KEY;
}
/**
* The key used to expose {@link Errors} in the core, in the case that message handling fails. Defaults to
* "errors".
*
* @param errorsKey The key value to set.
*/
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 <code>spring.integration.http.handler.error</code>. 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;
}
/**
* Specifies a SpEL expression to evaluate in order to generate the view name.
* The EvaluationContext will be populated with the reply message as the root object,
*
* @param viewExpression The view expression.
*/
public void setViewExpression(Expression viewExpression) {
this.viewExpression = viewExpression;
}
@Override
protected void onInit() throws Exception {
super.onInit();
this.evaluationContext = this.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.
*/
@Override
public final ModelAndView handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws Exception {
ModelAndView modelAndView = new ModelAndView();
try {
Message<?> replyMessage = super.doHandleRequest(servletRequest, servletResponse);
ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
if (replyMessage != null) {
Object reply = setupResponseAndConvertReply(response, replyMessage);
response.close();
modelAndView.addObject(this.replyKey, reply);
}
else {
setStatusCodeIfNeeded(response);
}
if (this.viewExpression != null) {
Object view;
if (replyMessage != null) {
view = this.viewExpression.getValue(this.evaluationContext, replyMessage);
}
else {
view = this.viewExpression.getValue(this.evaluationContext);
}
if (view instanceof View) {
modelAndView.setView((View) view);
}
else if (view instanceof String) {
modelAndView.setViewName((String) view);
}
else {
throw new IllegalStateException("view expression must resolve to a View or String");
}
}
}
catch (Exception e) {
MapBindingResult errors = new MapBindingResult(new HashMap<String, Object>(), "dummy");
PrintWriter stackTrace = new PrintWriter(new StringWriter());
e.printStackTrace(stackTrace);
errors.reject(this.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(this.errorsKey, errors);
}
return modelAndView;
}
}