- Revision
- 623
- Author
- mauro
- Date
- 2008-04-18 11:39:57 -0500 (Fri, 18 Apr 2008)
Log Message
WAFFLE-71: Refactored WaffleServlet to handle fatal errors in a more user-friendly way. On non-recoverable errors (such as when no context container or controller are found) an errors view is built using a configurable errors view template name (which defaults to "errors").
Modified Paths
- trunk/examples/simple-example/src/main/webapp/WEB-INF/web.xml
- trunk/waffle-core/src/main/java/org/codehaus/waffle/Constants.java
- trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ContextControllerDefinitionFactory.java
- trunk/waffle-core/src/main/java/org/codehaus/waffle/servlet/WaffleServlet.java
- trunk/waffle-core/src/test/java/org/codehaus/waffle/servlet/WaffleServletTest.java
Added Paths
Diff
Modified: trunk/examples/simple-example/src/main/webapp/WEB-INF/web.xml (622 => 623)
--- trunk/examples/simple-example/src/main/webapp/WEB-INF/web.xml 2008-04-18 14:14:23 UTC (rev 622) +++ trunk/examples/simple-example/src/main/webapp/WEB-INF/web.xml 2008-04-18 16:39:57 UTC (rev 623) @@ -53,6 +53,10 @@ <servlet> <servlet-name>waffle</servlet-name> <servlet-class>org.codehaus.waffle.servlet.WaffleServlet</servlet-class> + <init-param> + <param-name>errors.view</param-name> + <param-value>errors</param-value> + </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping>
Added: trunk/examples/simple-example/src/main/webapp/errors.jspx (0 => 623)
--- trunk/examples/simple-example/src/main/webapp/errors.jspx (rev 0) +++ trunk/examples/simple-example/src/main/webapp/errors.jspx 2008-04-18 16:39:57 UTC (rev 623) @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:jsp="http://java.sun.com/JSP/Page" + xmlns:c="http://java.sun.com/jsp/jstl/core"> + +<jsp:output doctype-root-element="html" + doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" + doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/> +<jsp:directive.page contentType="text/html;charset=UTF-8"/> + +<head> + <title>Waffle Errors</title> + <style type="text/css" title="currentStyle" media="screen"> + @import "stylesheets/style.css"; + </style> + <script src="" type="text/_javascript_"> + // + </script> +</head> + +<body> +<div id="errors"> +<p>Waffle has detected the following errors</p> +<ul> + <c:forEach items="${errors.allErrorMessages}" var="error" + xmlns:c="http://java.sun.com/jsp/jstl/core"> + <li>${error.message}</li> + </c:forEach> +</ul> +</div> +</body> +</html> \ No newline at end of file
Modified: trunk/waffle-core/src/main/java/org/codehaus/waffle/Constants.java (622 => 623)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/Constants.java 2008-04-18 14:14:23 UTC (rev 622) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/Constants.java 2008-04-18 16:39:57 UTC (rev 623) @@ -19,6 +19,7 @@ String SESSION_CONTAINER_KEY = "waffle.session.container"; String VIEW_PREFIX_KEY = "view.prefix"; String VIEW_SUFFIX_KEY = "view.suffix"; + String ERRORS_VIEW_KEY = "errors.view"; String CONTROLLER_KEY = "controller"; String ERRORS_KEY = "errors"; String MESSAGES_KEY = "messages";
Added: trunk/waffle-core/src/main/java/org/codehaus/waffle/context/ContextContainerNotFoundException.java (0 => 623)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/context/ContextContainerNotFoundException.java (rev 0) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/context/ContextContainerNotFoundException.java 2008-04-18 16:39:57 UTC (rev 623) @@ -0,0 +1,17 @@ +package org.codehaus.waffle.context; + +import org.codehaus.waffle.WaffleException; + +/** + * Thrown when context controller is not found in request + * + * @author Mauro Talevi + */ [EMAIL PROTECTED]("serial") +public class ContextContainerNotFoundException extends WaffleException { + + public ContextContainerNotFoundException(String message) { + super(message); + } + +}
Modified: trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ContextControllerDefinitionFactory.java (622 => 623)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ContextControllerDefinitionFactory.java 2008-04-18 14:14:23 UTC (rev 622) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ContextControllerDefinitionFactory.java 2008-04-18 16:39:57 UTC (rev 623) @@ -15,11 +15,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.codehaus.waffle.WaffleException; import org.codehaus.waffle.action.MethodDefinition; import org.codehaus.waffle.action.MethodDefinitionFinder; import org.codehaus.waffle.action.MissingActionMethodException; import org.codehaus.waffle.context.ContextContainer; +import org.codehaus.waffle.context.ContextContainerNotFoundException; import org.codehaus.waffle.context.RequestLevelContainer; import org.codehaus.waffle.monitor.ControllerMonitor; @@ -69,17 +69,13 @@ if (requestLevelContainer == null) { controllerMonitor.requestContextContainerNotFound(); - String error = "No context container found at request level. " - + "Please ensure that a WaffleRequestFilter is registered in the web.xml"; - throw new WaffleException(error); + throw new ContextContainerNotFoundException("No Waffle context container found at request level. WaffleRequestFilter must be registered in the web.xml"); } Object controller = requestLevelContainer.getComponentInstance(name); if (controller == null) { controllerMonitor.controllerNotFound(name); - String error = "No controller '" + name + "' configured for the specified path: '" - + request.getRequestURI() + ". Please ensure that controller '" + name + "' is registered in the Registrar."; - throw new WaffleException(error); + throw new ControllerNotFoundException(("No controller '" + name + "' is configured in the Registrar for the request path '"+request.getRequestURI()+"'")); } return controller;
Added: trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ControllerNotFoundException.java (0 => 623)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ControllerNotFoundException.java (rev 0) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/controller/ControllerNotFoundException.java 2008-04-18 16:39:57 UTC (rev 623) @@ -0,0 +1,17 @@ +package org.codehaus.waffle.controller; + +import org.codehaus.waffle.WaffleException; + +/** + * Thrown when controller is not found in registrar + * + * @author Mauro Talevi + */ [EMAIL PROTECTED]("serial") +public class ControllerNotFoundException extends WaffleException { + + public ControllerNotFoundException(String message) { + super(message); + } + +}
Modified: trunk/waffle-core/src/main/java/org/codehaus/waffle/servlet/WaffleServlet.java (622 => 623)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/servlet/WaffleServlet.java 2008-04-18 14:14:23 UTC (rev 622) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/servlet/WaffleServlet.java 2008-04-18 16:39:57 UTC (rev 623) @@ -11,6 +11,7 @@ package org.codehaus.waffle.servlet; import static java.util.Arrays.asList; +import static org.codehaus.waffle.Constants.ERRORS_VIEW_KEY; import static org.codehaus.waffle.Constants.VIEW_PREFIX_KEY; import static org.codehaus.waffle.Constants.VIEW_SUFFIX_KEY; @@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; import org.codehaus.waffle.ComponentRegistry; +import org.codehaus.waffle.WaffleException; import org.codehaus.waffle.action.ActionMethodExecutor; import org.codehaus.waffle.action.ActionMethodInvocationException; import org.codehaus.waffle.action.ActionMethodResponse; @@ -55,8 +57,9 @@ @SuppressWarnings("serial") public class WaffleServlet extends HttpServlet { + private static final String DEFAULT_VIEW_PREFIX = "/"; private static final String DEFAULT_VIEW_SUFFIX = ".jspx"; - private static final String DEFAULT_VIEW_PREFIX = "/"; + private static final String DEFAULT_ERRORS_VIEW = "errors"; private static final String EMPTY = ""; private static final String POST = "POST"; private ActionMethodExecutor actionMethodExecutor; @@ -68,6 +71,7 @@ private Validator validator; private String viewPrefix; private String viewSuffix; + private String errorsView; private boolean componentsRetrieved = false; /** @@ -106,16 +110,10 @@ } public void init() throws ServletException { - viewPrefix = getInitParameter(VIEW_PREFIX_KEY); - if (viewPrefix == null || viewPrefix.equals(EMPTY)) { - viewPrefix = DEFAULT_VIEW_PREFIX; // default - } + viewPrefix = initParam(VIEW_PREFIX_KEY, DEFAULT_VIEW_PREFIX); + viewSuffix = initParam(VIEW_SUFFIX_KEY, DEFAULT_VIEW_SUFFIX); + errorsView = initParam(ERRORS_VIEW_KEY, DEFAULT_ERRORS_VIEW); - viewSuffix = getInitParameter(VIEW_SUFFIX_KEY); - if (viewSuffix == null || viewSuffix.equals(EMPTY)) { - viewSuffix = DEFAULT_VIEW_SUFFIX; // default - } - if (!componentsRetrieved) { // Retrieve instance components from the ComponentRegistry ComponentRegistry registry = getComponentRegistry(); @@ -129,29 +127,19 @@ } } + private String initParam(String key, String defaultValue) { + String value = getInitParameter(key); + if (value == null || value.equals(EMPTY)) { + value = defaultValue; // default + } + return value; + } + private ComponentRegistry getComponentRegistry() { return ServletContextHelper.getComponentRegistry(getServletContext()); } /** - * Obtain the controller defition the user is requesting. - * - * @param request the HttpServletRequest - * @param response the HttpServletResponse - * @return A ControllerDefinition - * @throws ServletException if controller not found - */ - protected ControllerDefinition getControllerDefinition(HttpServletRequest request, - HttpServletResponse response) throws ServletException { - ControllerDefinition controllerDefinition = controllerDefinitionFactory.getControllerDefinition(request, response); - if (controllerDefinition.getController() == null) { - throw new ServletException("Unable to locate the Waffle Controller: " + request.getServletPath()); - } - - return controllerDefinition; - } - - /** * Responsible for servicing the requests from the users. * * @param request the HttpServletResponse @@ -165,52 +153,55 @@ ContextContainer requestContainer = RequestLevelContainer.get(); ErrorsContext errorsContext = requestContainer.getComponentInstanceOfType(ErrorsContext.class); - ControllerDefinition controllerDefinition = getControllerDefinition(request, response); - dataBinder.bind(request, response, errorsContext, controllerDefinition.getController()); - validator.validate(controllerDefinition, errorsContext); - ActionMethodResponse actionMethodResponse = new ActionMethodResponse(); View view = null; - try { + ControllerDefinition controllerDefinition = controllerDefinitionFactory.getControllerDefinition(request, + response); + dataBinder.bind(request, response, errorsContext, controllerDefinition.getController()); + validator.validate(controllerDefinition, errorsContext); + try { - if (errorsContext.hasErrorMessages() || noMethodDefinition(controllerDefinition)) { - view = buildReferringView(controllerDefinition); - } else { - actionMethodExecutor.execute(actionMethodResponse, controllerDefinition); + if (errorsContext.hasErrorMessages() || noMethodDefinition(controllerDefinition)) { + view = buildReferringView(controllerDefinition); + } else { + actionMethodExecutor.execute(actionMethodResponse, controllerDefinition); - if (errorsContext.hasErrorMessages()) { - view = buildReferringView(controllerDefinition); - } else if (actionMethodResponse.getReturnValue() == null) { - // Null or VOID indicate a Waffle convention (return to referring page) - // unless PRG is disabled - if (request.getMethod().equalsIgnoreCase(POST)) { - if ( usePRG(controllerDefinition.getMethodDefinition()) ){ - // PRG (Post/Redirect/Get): see http://en.wikipedia.org/wiki/Post/Redirect/Get - view = buildRedirectingView(request, controllerDefinition); - } else { - // PRG is disabled + if (errorsContext.hasErrorMessages()) { + view = buildReferringView(controllerDefinition); + } else if (actionMethodResponse.getReturnValue() == null) { + // Null or VOID indicate a Waffle convention (return to referring page) + // unless PRG is disabled + if (request.getMethod().equalsIgnoreCase(POST)) { + if (usePRG(controllerDefinition.getMethodDefinition())) { + // PRG (Post/Redirect/Get): see http://en.wikipedia.org/wiki/Post/Redirect/Get + view = buildRedirectingView(request, controllerDefinition); + } else { + // PRG is disabled + view = buildReferringView(controllerDefinition); + } + } else { // was a GET view = buildReferringView(controllerDefinition); } - } else { // was a GET - view = buildReferringView(controllerDefinition); } } + + } catch (ActionMethodInvocationException e) { + errorsContext.addErrorMessage(new GlobalErrorMessage("Action method invocation failed for controller " + + controllerDefinition.getName() + ", :" + e.getMessage(), e)); + view = buildReferringView(controllerDefinition); + servletMonitor.actionMethodInvocationFailed(e); } - - } catch (ActionMethodInvocationException e) { - errorsContext.addErrorMessage(new GlobalErrorMessage("Action method invocation failed: "+e.getMessage(), e)); - view = buildReferringView(controllerDefinition); - servletMonitor.actionMethodInvocationFailed(e); + requestAttributeBinder.bind(request, controllerDefinition.getController()); + } catch (WaffleException e) { + errorsContext.addErrorMessage(new GlobalErrorMessage(e.getMessage(), e)); + view = buildErrorsView(request); } if (view != null) { actionMethodResponse.setReturnValue(view); } - - requestAttributeBinder.bind(request, controllerDefinition.getController()); actionMethodResponseHandler.handle(request, response, actionMethodResponse); - } private boolean noMethodDefinition(ControllerDefinition controllerDefinition) { @@ -267,5 +258,15 @@ return new RedirectView(url, controllerDefinition.getController()); } + /** + * Builds the errors view, for cases in which the context container or the controller are not found. + * The user can extend and override behaviour, eg to throw a ServletException. + * + * @param request the HttpServletRequest + * @return The View + */ + protected View buildErrorsView(HttpServletRequest request) throws ServletException { + return buildReferringView(new ControllerDefinition(errorsView, null, null)); + } }
Modified: trunk/waffle-core/src/test/java/org/codehaus/waffle/servlet/WaffleServletTest.java (622 => 623)
--- trunk/waffle-core/src/test/java/org/codehaus/waffle/servlet/WaffleServletTest.java 2008-04-18 14:14:23 UTC (rev 622) +++ trunk/waffle-core/src/test/java/org/codehaus/waffle/servlet/WaffleServletTest.java 2008-04-18 16:39:57 UTC (rev 623) @@ -45,6 +45,7 @@ import org.codehaus.waffle.context.RequestLevelContainer; import org.codehaus.waffle.controller.ControllerDefinition; import org.codehaus.waffle.controller.ControllerDefinitionFactory; +import org.codehaus.waffle.controller.ControllerNotFoundException; import org.codehaus.waffle.i18n.DefaultMessagesContext; import org.codehaus.waffle.i18n.MessagesContext; import org.codehaus.waffle.monitor.ServletMonitor; @@ -78,6 +79,8 @@ will(returnValue(null)); one(servletConfig).getInitParameter(Constants.VIEW_SUFFIX_KEY); will(returnValue(".jsp")); + one(servletConfig).getInitParameter(Constants.ERRORS_VIEW_KEY); + will(returnValue("errors")); }}); // Mock ComponentRegistry @@ -174,6 +177,13 @@ one(requestAttributeBinder).bind(with(same(request)), with(any(NonDispatchingController.class))); }}); + // Mock ControllerDefinitionFactory + final ControllerDefinitionFactory controllerDefinitionFactory = mockery.mock(ControllerDefinitionFactory.class); + mockery.checking(new Expectations() {{ + one(controllerDefinitionFactory).getControllerDefinition(with(same(request)), with(same(response))); + will(returnValue(new ControllerDefinition("no name", nonDispatchingController, methodDefinition))); + }}); + // stub out what we don't want called ... execute it SilentMonitor monitor = new SilentMonitor(); WaffleServlet servlet = new WaffleServlet(new InterceptingActionMethodExecutor(monitor), @@ -181,12 +191,8 @@ monitor, new OgnlDataBinder(new OgnlValueConverterFinder(), null, monitor), requestAttributeBinder, - null, validator) { + controllerDefinitionFactory, validator) { @Override - protected ControllerDefinition getControllerDefinition(HttpServletRequest request, HttpServletResponse response) { - return new ControllerDefinition("no name", nonDispatchingController, methodDefinition); - } - @Override public ServletConfig getServletConfig() { return servletConfig; } @@ -257,6 +263,13 @@ one(requestAttributeBinder).bind(with(same(request)), with(any(NonDispatchingController.class))); }}); + // Mock ControllerDefinitionFactory + final ControllerDefinitionFactory controllerDefinitionFactory = mockery.mock(ControllerDefinitionFactory.class); + mockery.checking(new Expectations() {{ + one(controllerDefinitionFactory).getControllerDefinition(with(same(request)), with(same(response))); + will(returnValue(new ControllerDefinition("no name", nonDispatchingController, methodDefinition))); + }}); + // stub out what we don't want called ... execute it SilentMonitor monitor = new SilentMonitor(); WaffleServlet servlet = new WaffleServlet(new InterceptingActionMethodExecutor(monitor), @@ -264,12 +277,8 @@ monitor, new OgnlDataBinder(new OgnlValueConverterFinder(), null, monitor), requestAttributeBinder, - null, validator) { + controllerDefinitionFactory, validator) { @Override - protected ControllerDefinition getControllerDefinition(HttpServletRequest request, HttpServletResponse response) { - return new ControllerDefinition("no name", nonDispatchingController, methodDefinition); - } - @Override public ServletConfig getServletConfig() { return servletConfig; } @@ -280,20 +289,16 @@ } @SuppressWarnings({ "serial", "unchecked" }) - @Test(expected = ServletException.class) - public void cannotServiceIfControllerNotFound() throws Exception { + @Test + public void canHandleControllerNotFound() throws Exception { // Mock ErrorsContext final ErrorsContext errorsContext = mockery.mock(ErrorsContext.class); final ContextContainer contextContainer = mockery.mock(ContextContainer.class); mockery.checking(new Expectations() { { - one(contextContainer).getComponentInstanceOfType(ErrorsContext.class); will(returnValue(errorsContext)); - one(errorsContext).hasErrorMessages(); - will(returnValue(false)); - one(contextContainer).getAllComponentInstancesOfType(MethodInterceptor.class); - will(returnValue(new ArrayList<Object>())); + one(errorsContext).addErrorMessage(with(any(ErrorMessage.class))); } }); @@ -310,21 +315,39 @@ mockery.checking(new Expectations() {{ atLeast(1).of(request).getParameterNames(); will(returnValue(enumeration)); - one(request).getServletPath(); - will(returnValue("/foobar")); }}); + + // Mock HttpServletResponse + final HttpServletResponse response = mockery.mock(HttpServletResponse.class); + + // Mock ActionMethodResponseHandler + final ActionMethodResponseHandler actionMethodResponseHandler = mockery.mock(ActionMethodResponseHandler.class); + mockery.checking(new Expectations() {{ + one(actionMethodResponseHandler).handle(with(same(request)), with(same(response)), with(any(ActionMethodResponse.class))); + }}); // Mock ControllerDefinitionFactory final ControllerDefinitionFactory controllerDefinitionFactory = mockery.mock(ControllerDefinitionFactory.class); mockery.checking(new Expectations() {{ - one(controllerDefinitionFactory).getControllerDefinition(request, null); - will(returnValue(new ControllerDefinition("junk", null, null))); + one(controllerDefinitionFactory).getControllerDefinition(request, response); + will(throwException(new ControllerNotFoundException("No controller found "))); }}); - WaffleServlet servlet = new WaffleServlet(){ + + // stub out what we don't want called ... execute it + WaffleServlet servlet = new WaffleServlet(null, + actionMethodResponseHandler, + new SilentMonitor(), + new OgnlDataBinder(new OgnlValueConverterFinder(), null, new SilentMonitor()), + null, + controllerDefinitionFactory, null) { @Override public ServletConfig getServletConfig() { return servletConfig; + } + @Override + public void log(String string) { + // ignore } }; @@ -343,7 +366,7 @@ servletMonitorField.setAccessible(true); servletMonitorField.set(servlet, servletMonitor); - servlet.service(request, null); + servlet.service(request, response); } @SuppressWarnings("serial") @@ -397,18 +420,21 @@ one(requestAttributeBinder).bind(with(same(request)), with(any(NonDispatchingController.class))); }}); + // Mock ControllerDefinitionFactory + final ControllerDefinitionFactory controllerDefinitionFactory = mockery.mock(ControllerDefinitionFactory.class); + mockery.checking(new Expectations() {{ + one(controllerDefinitionFactory).getControllerDefinition(with(same(request)), with(same(response))); + will(returnValue(new ControllerDefinition("no name", nonDispatchingController, null))); + }}); + // stub out what we don't want called ... execute it WaffleServlet servlet = new WaffleServlet(null, actionMethodResponseHandler, new SilentMonitor(), new OgnlDataBinder(new OgnlValueConverterFinder(), null, new SilentMonitor()), requestAttributeBinder, - null, validator) { + controllerDefinitionFactory, validator) { @Override - protected ControllerDefinition getControllerDefinition(HttpServletRequest request, HttpServletResponse response) { - return new ControllerDefinition("no name", nonDispatchingController, null); - } - @Override public ServletConfig getServletConfig() { return servletConfig; } @@ -478,18 +504,21 @@ one(requestAttributeBinder).bind(with(same(request)), with(any(NonDispatchingController.class))); }}); + // Mock ControllerDefinitionFactory + final ControllerDefinitionFactory controllerDefinitionFactory = mockery.mock(ControllerDefinitionFactory.class); + mockery.checking(new Expectations() {{ + one(controllerDefinitionFactory).getControllerDefinition(with(same(request)), with(same(response))); + will(returnValue(new ControllerDefinition("no name", nonDispatchingController, methodDefinition))); + }}); + // stub out what we don't want called ... execute it WaffleServlet servlet = new WaffleServlet(null, actionMethodResponseHandler, new SilentMonitor(), new OgnlDataBinder(new OgnlValueConverterFinder(), null, new SilentMonitor()), requestAttributeBinder, - null, validator) { + controllerDefinitionFactory, validator) { @Override - protected ControllerDefinition getControllerDefinition(HttpServletRequest request, HttpServletResponse response) { - return new ControllerDefinition("no name", nonDispatchingController, methodDefinition); - } - @Override public ServletConfig getServletConfig() { return servletConfig; } @@ -563,6 +592,8 @@ one(servletConfig).getInitParameter(Constants.VIEW_PREFIX_KEY); will(returnValue("/WEB-INF/jsp")); one(servletConfig).getInitParameter(Constants.VIEW_SUFFIX_KEY); + one(servletConfig).getInitParameter(Constants.ERRORS_VIEW_KEY); + will(returnValue("errors")); }}); WaffleServlet servlet = new WaffleServlet() {
To unsubscribe from this list please visit:
