
net.sourceforge.stripes.controller.DispatcherServlet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stripes Show documentation
Show all versions of stripes Show documentation
Stripes web framework jar, including tag library.
/* Copyright 2005-2006 Tim Fennell
*
* 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 net.sourceforge.stripes.controller;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.AsyncResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.util.HttpUtil;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.validation.BooleanTypeConverter;
import net.sourceforge.stripes.validation.expression.ExpressionValidator;
import net.sourceforge.stripes.validation.expression.Jsp20ExpressionExecutor;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import javax.servlet.jsp.PageContext;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Stack;
/**
* Servlet that controls how requests to the Stripes framework are processed. Uses an instance of
* the ActionResolver interface to locate the bean and method used to handle the current request and
* then delegates processing to the bean.
*
* While the DispatcherServlet is structured so that it can be easily subclassed and
* overridden much of the processing work is delegated to the {@link DispatcherHelper} class.
*
* @author Tim Fennell
*/
@WebServlet(asyncSupported = true)
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Configuration key used to lookup up a property that determines whether or not beans'
* custom validate() method gets invoked when validation errors are generated during
* the binding process
*/
public static final String RUN_CUSTOM_VALIDATION_WHEN_ERRORS =
"Validation.InvokeValidateWhenErrorsExist";
private Boolean alwaysInvokeValidate;
/** Log used throughout the class. */
private static final Log log = Log.getInstance(DispatcherServlet.class);
/**
* Invokes the following instance level methods in order to coordinate the processing
* of requests:
*
*
* - {@link #resolveActionBean(ExecutionContext)}
* - {@link #resolveHandler(ExecutionContext)}
* - {@link #doBindingAndValidation(ExecutionContext)}
* - {@link #doCustomValidation(ExecutionContext)}
* - {@link #handleValidationErrors(ExecutionContext)}
* - {@link #invokeEventHandler(ExecutionContext)}
*
*
* If any of the above methods return a {@link Resolution} the rest of the request processing
* is aborted and the resolution is executed.
*
* @param request the HttpServletRequest handed to the class by the container
* @param response the HttpServletResponse paired to the request
* @throws ServletException thrown when the system fails to process the request in any way
*/
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException {
// It sucks that we have to do this here (in the request cycle), but there doesn't
// seem to be a good way to get at the Configuration from the Filter in init()
doOneTimeConfiguration();
///////////////////////////////////////////////////////////////////////
// Here beings the real processing of the request!
///////////////////////////////////////////////////////////////////////
log.trace("Dispatching request to URL: ", HttpUtil.getRequestedPath(request));
PageContext pageContext = null;
final ExecutionContext ctx = new ExecutionContext();
boolean async = false;
try {
final Configuration config = StripesFilter.getConfiguration();
// First manufacture an ActionBeanContext
final ActionBeanContext context =
config.getActionBeanContextFactory().getContextInstance(request, response);
context.setServletContext(getServletContext());
// Then setup the ExecutionContext that we'll use to process this request
ctx.setActionBeanContext(context);
try {
ActionBeanContext abc = ctx.getActionBeanContext();
// It's unclear whether this usage of the JspFactory will work in all containers. It looks
// like it should, but still, we should be careful not to screw up regular request
// processing if it should fail. Why do we do this? So we can have a container-agnostic
// way of getting an ExpressionEvaluator to do expression based validation. And we only
// need it if the Jsp20 executor is used, so maybe soon we can kill it?
if (ExpressionValidator.getExecutor() instanceof Jsp20ExpressionExecutor) {
pageContext = JspFactory.getDefaultFactory().getPageContext(this, // the servlet inst
abc.getRequest(), // req
abc.getResponse(), // res
null, // error page url
(request.getSession(false) != null), // needsSession - don't force a session creation if one doesn't already exist
abc.getResponse().getBufferSize(),
true); // autoflush
DispatcherHelper.setPageContext(pageContext);
}
}
catch (Exception e) {
// Don't even log this, this failure gets reported if action beans actually
// try and make use of expression validation, otherwise this is just noise
}
// Resolve the ActionBean, and if an interceptor returns a resolution, bail now
saveActionBean(request);
Resolution resolution = requestInit(ctx);
if (resolution == null) {
resolution = resolveActionBean(ctx);
if (resolution == null) {
resolution = resolveHandler(ctx);
if (resolution == null) {
// Then run binding and validation
resolution = doBindingAndValidation(ctx);
if (resolution == null) {
// Then continue on to custom validation
resolution = doCustomValidation(ctx);
if (resolution == null) {
// And then validation error handling
resolution = handleValidationErrors(ctx);
if (resolution == null) {
// And finally invoking of the event handler
resolution = invokeEventHandler(ctx);
}
}
}
}
}
}
// Whatever stage it came from, execute the resolution
if (resolution != null) {
if (resolution instanceof AsyncResolution) {
// special handling for async resolutions
async = true;
// remove currentContext ThreadLocal
ExecutionContext.clearContextThreadLocal();
// start async processing
AsyncContext asyncContext = request.startAsync();
final PageContext pc = pageContext;
// register listener for finalizing the async processing
asyncContext.addListener(new AsyncListener() {
// TODO factor out and make sure this finalizations are needed...
public void onComplete(AsyncEvent event) throws IOException {
if (pc != null) {
JspFactory.getDefaultFactory().releasePageContext(pc);
DispatcherHelper.setPageContext(null);
}
requestComplete(ctx);
restoreActionBean(request);
}
public void onTimeout(AsyncEvent event) throws IOException {
if (pc != null) {
JspFactory.getDefaultFactory().releasePageContext(pc);
DispatcherHelper.setPageContext(null);
}
requestComplete(ctx);
restoreActionBean(request);
}
public void onError(AsyncEvent event) throws IOException {
if (pc != null) {
JspFactory.getDefaultFactory().releasePageContext(pc);
DispatcherHelper.setPageContext(null);
}
requestComplete(ctx);
restoreActionBean(request);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
AsyncResolution asyncResolution = (AsyncResolution)resolution;
asyncResolution.setAsyncContext(asyncContext);
}
executeResolution(ctx, resolution);
}
}
catch (ServletException se) { throw se; }
catch (RuntimeException re) { throw re; }
catch (InvocationTargetException ite) {
if (ite.getTargetException() instanceof ServletException) {
throw (ServletException) ite.getTargetException();
}
else if (ite.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) ite.getTargetException();
}
else {
throw new StripesServletException
("ActionBean execution threw an exception.", ite.getTargetException());
}
}
catch (Exception e) {
throw new StripesServletException("Exception encountered processing request.", e);
}
finally {
// Make sure to release the page context
if (!async) {
if (pageContext != null) {
JspFactory.getDefaultFactory().releasePageContext(pageContext);
DispatcherHelper.setPageContext(null);
}
requestComplete(ctx);
restoreActionBean(request);
}
}
}
/**
* Calls interceptors listening for RequestInit. There is no Stripes code that
* executes for this lifecycle stage.
*/
private Resolution requestInit(ExecutionContext ctx) throws Exception {
ctx.setLifecycleStage(LifecycleStage.RequestInit);
ctx.setInterceptors(StripesFilter.getConfiguration().getInterceptors(LifecycleStage.RequestInit));
return ctx.wrap(new Interceptor() {public Resolution intercept(ExecutionContext context) throws Exception {return null;}});
}
/**
* Calls interceptors listening for RequestComplete. There is no Stripes code
* that executes for this lifecycle stage. In addition, any response from
* interceptors is ignored because it is too late to execute a Resolution at
* this point.
*/
private void requestComplete(ExecutionContext ctx) {
ctx.setLifecycleStage(LifecycleStage.RequestComplete);
ctx.setInterceptors(StripesFilter.getConfiguration().getInterceptors(LifecycleStage.RequestComplete));
try {
Resolution resolution = ctx.wrap(new Interceptor() {public Resolution intercept(ExecutionContext context) throws Exception {return null;}});
if (resolution != null)
log.warn("Resolutions returned from interceptors for ", ctx.getLifecycleStage(),
" are ignored because it is too late to execute them.");
}
catch (Exception e) {
log.error(e);
}
}
/**
* Responsible for resolving the ActionBean for the current request. Delegates to
* {@link DispatcherHelper#resolveActionBean(ExecutionContext)}.
*/
protected Resolution resolveActionBean(ExecutionContext ctx) throws Exception {
return DispatcherHelper.resolveActionBean(ctx);
}
/**
* Responsible for resolving the event handler method for the current request. Delegates to
* {@link DispatcherHelper#resolveHandler(ExecutionContext)}.
*/
protected Resolution resolveHandler(ExecutionContext ctx) throws Exception {
return DispatcherHelper.resolveHandler(ctx);
}
/**
* Responsible for executing binding and validation for the current request. Delegates to
* {@link DispatcherHelper#doBindingAndValidation(ExecutionContext, boolean)}.
*/
protected Resolution doBindingAndValidation(ExecutionContext ctx) throws Exception {
return DispatcherHelper.doBindingAndValidation(ctx, true);
}
/**
* Responsible for executing custom validation methods for the current request. Delegates to
* {@link DispatcherHelper#doCustomValidation(ExecutionContext, boolean)}.
*/
protected Resolution doCustomValidation(ExecutionContext ctx) throws Exception {
return DispatcherHelper.doCustomValidation(ctx, alwaysInvokeValidate);
}
/**
* Responsible for handling any validation errors that arise during validation. Delegates to
* {@link DispatcherHelper#handleValidationErrors(ExecutionContext)}.
*/
protected Resolution handleValidationErrors(ExecutionContext ctx) throws Exception {
return DispatcherHelper.handleValidationErrors(ctx);
}
/**
* Responsible for invoking the event handler if no validation errors occur. Delegates to
* {@link DispatcherHelper#invokeEventHandler(ExecutionContext)}.
*/
protected Resolution invokeEventHandler(ExecutionContext ctx) throws Exception {
return DispatcherHelper.invokeEventHandler(ctx);
}
/**
* Responsible for executing the Resolution for the current request. Delegates to
* {@link DispatcherHelper#executeResolution(ExecutionContext, Resolution)}.
*/
protected void executeResolution(ExecutionContext ctx, Resolution resolution) throws Exception {
DispatcherHelper.executeResolution(ctx, resolution);
}
/**
* Performs a simple piece of one time configuration that requires access to the
* Configuration object delivered through the Stripes Filter.
*/
private void doOneTimeConfiguration() {
if (alwaysInvokeValidate == null) {
// Check to see if, in this application, validate() methods should always be run
// even when validation errors already exist
String callValidateWhenErrorsExist = StripesFilter.getConfiguration()
.getBootstrapPropertyResolver().getProperty(RUN_CUSTOM_VALIDATION_WHEN_ERRORS);
if (callValidateWhenErrorsExist != null) {
BooleanTypeConverter c = new BooleanTypeConverter();
this.alwaysInvokeValidate = c.convert(callValidateWhenErrorsExist, Boolean.class, null);
}
else {
this.alwaysInvokeValidate = false; // Default behaviour
}
}
}
/**
* Fetches, and lazily creates if required, a Stack in the request to store ActionBeans
* should the current request involve forwards or includes to other ActionBeans.
*
* @param request the current HttpServletRequest
* @return the Stack if present, or if creation is requested
*/
@SuppressWarnings("unchecked")
protected Stack getActionBeanStack(HttpServletRequest request, boolean create) {
Stack stack = (Stack) request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN_STACK);
if (stack == null && create) {
stack = new Stack();
request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN_STACK, stack);
}
return stack;
}
/**
* Saves the current value of the 'actionBean' attribute in the request so that it
* can be restored at a later date by calling {@link #restoreActionBean(HttpServletRequest)}.
* If no ActionBean is currently stored in the request, nothing is changed.
*
* @param request the current HttpServletRequest
*/
protected void saveActionBean(HttpServletRequest request) {
if (request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN) != null) {
Stack stack = getActionBeanStack(request, true);
stack.push((ActionBean) request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN));
}
}
/**
* Restores the previous value of the 'actionBean' attribute in the request. If no
* ActionBeans have been saved using {@link #saveActionBean(HttpServletRequest)} then this
* method has no effect.
*
* @param request the current HttpServletRequest
*/
protected void restoreActionBean(HttpServletRequest request) {
Stack stack = getActionBeanStack(request, false);
if (stack != null && !stack.empty()) {
request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN, stack.pop());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy