Lifecycles Etc.

ActionBean/Request Lifecycle

The request lifecycle for a request that targets an ActionBean (i.e. is resolved to the Stripes Dispatcher Servlet) is fairly complicated. At a high level it can be seen as:

  1. Resolve an ActionBean based on the URL of the request and set the ActionBeanContext on it
  2. Resolve the Handler method that will handle the event received in the request
  3. Bind properties from the HttpServletRequest into the ActionBean, running validation as necessary
  4. Invoke any custom validation methods
  5. Invoke the appropriate handler method on the ActionBean
  6. If the ActionBean returns a non-null Resolution, execute it

But this is a high level view of what happens, and misses a lot of details. The following sections expand on this and provide those details.

Stage 0: Preprocessing by the Stripes Filter

In a correctly configured web application all requests to Stripes, and for JSPs, are routed through the Stripes Filter which provides several necessary services.

Firstly it "hides" the current Configuration somewhere, so that anywhere in the application the Configuration can be retrieved by calling StripesFilter.getConfiguration()

Secondly it resolves the Locale that should be used for the current request. It does this by invoking the configured LocalePicker.

Thirdly it wraps the HttpServletRequest with a StripesRequestWrapper. The wrapper ensures that HttpServletRequest.getLocale() always returns the picked Locale. The wrapper is also responsible for detecting when the request is a multipart/form-data request and correctly parsing such requests to provide access to the request parameters and uploaded files.

At this point the flow of control could flow directly to a JSP. In this case the request lifecycle continues on just like any other JSP request. If the request is for an ActionBean event, then we continue on to the Stripes DispatcherServlet.

Stage 1: Resolving (and Creating) the ActionBean

Firstly the DispatcherServlet fetches the configured ActionBeanContextFactory and uses it to manufacture an ActionBeanContext. The DefaultActionBeanContextFactory looks for a configured class name, and if none is found, creates a new ActionBeanContext.

Safer interactions with Request, Response and Session

Leveraging the DefaultActionBeanContextFactory to supply your own application specific subclass of ActionBeanContext allows you to centralize all of your access to HttpSession, HttpServletRequest and HttpServletResponse in a single place. This has the advantage of making your application much easier to test as you can always substitute a test version of your ActionBeanContext during test runs.

The DispatcherServlet then fetches the configured ActionResolver and uses it to resolve the appropriate ActionBean instance. The default ActionResolver is the NameBasedActionResolver. This resolves an ActionBean instance using the following logic:

  1. Match the URL path of the request to the URL binding of an ActionBean class
  2. If the ActionBean is annotated with @SessionScope then
    • Look for an instance in HttpSession using HttpSession.getAttribute(UrlBinding)
    • If an instance exists return it, otherwise create it, insert it into HttpSession
  3. Else the ActionBean is using the default (and highly recommeded) Request scope
    • Create an instance of the ActionBean
  4. Invoke setContext() on the ActionBean
  5. Insert the bean into the relevant scope (request or session) using the URL binding as the key
  6. Return the ActionBean instance

The DispatcherServlet then inserts the ActionBean into the request scope under the key 'actionBean' for convenience. This allows developers to easily identify the current/last executed ActionBean.

Stage 2: Handler Resolution

In this stage the DispatcherServlet uses the ActionResolver to determine the name of the event submitted. If there was no identifiable event name then the ActionResolver is asked for the @DefaultHandler method, otherwise it is asked for the method which handles the named event.

The event name is then set on the ActionBeanContext. Note that the correct name is set even when the default event is executed.

Stage 3: Binding and Field Validation

The process of binding and validation, while driven by Stripes, allows the ActionBean author to assert a certain amount of control. To better understand this section it is worth understanding the following interfaces and annotations first:

This section will provide an overview of validation as relevant to the ActionBean lifecycle. For more information please read the Validation Reference.

The first step involves the DispatcherServlet looking up the configured ActionBeanPropertyBinder and invoking it to perform field level validation and binding. The ActionBeanPropertyBinder is told by the dispatcher whether or not validation should be performed. The default ActionBeanPropertyBinder is the DefaultActionBeanPropertyBinder.

Even handlers with @DontValidate can produce validation errors

Specifying @DontValidate turns off all optional validation. However, binding, type conversion and validation are all inextricably linked. If a property needs to be type converted to be bound, and the conversion fails, it will produce a validation error. Therefore, if using @DontValidate for events that receive user input, keep in mind that validation errors may still occur.

At a high level the DefaultActionBeanPropertyBinder does the following:

  1. Performs required field validation on all required fields
  2. Perform pre-conversion validations like min/max lenght, mask checks etc.
  3. For each field supplied in the request that had a non-empty-string value
    • Convert the field using the type conversion system
    • Bind the converted values on to the ActionBean
  4. Run post conversion validations including min/max numeric value and expression checks
  5. Return a ValidationErrors containing any errors that arose during validation and binding

Stage 4: Custom Validation

Next the ActionBean is examined to determine which (if any) validation methods should be executed. Validation methods may be specified to run only when no errors have been generated so far, or always. An application level default exists and can be configured; if not configured the default is not to run validation methods when errors exist (this is done so that ActionBean authors can always rely on a consistent, validated ActionBean in validation methods, and not have to continually check for nulls and inconsistent state).

Handling of validation errors

When errors are discovered during validation and the ActionBean implements ValidationErrorHandler the handleValidationErrors(errors) method will be invoked. In this method bean authors may manipulate the collection of errors (perhaps emptying it, which has significant implications) and/or return an alternative Resolution. If a Resolution is returned it is executed immediatley.

At this point if there are no validation errors (because they were removed by handleValidationErrors()) then we skip to the end of validation and binding. If errors still exist we need to execute an appropriate Resolution. ActionBeanContext.getSourcePageResolution() is invoked to fetch the Resolution. In the default ActionBeanContext this returns a ForwardResolution corresponding to the page that originated the request. The Resolution is then executed, resulting the in the same page being re-displayed in the browser, and the form controls getting re-populated and rendered in error.

Stage 5: Executing the ActionBean

Assuming that everything went well up to this point, and no validation errors were created, the DispatcherServlet will invoke the handler method on the ActionBean. If the ActionBean throws an Exception this will be propogated by the DispatcherServlet - either directly if it is a Servlet or Runtime exception, or by wrapping it in a StripesServletException otherwise.

The ActionBean may execute arbitrary code, including handling the response directly - though this is not encouraged. Handler methods may return any Object, but the return is ignored unless it is an instance of Resolution. In this case, if the ActionBean returns a non-null Resolution the DispatcherServlet will call its execute() method to complete the request.