Chrysalis Error Handling

Chrysalis supports error handling at several different levels:

  • Client-side validations using generated JavaScript.
  • Basic server-side error handling by forwarding to error pages, as per the JSP specification.
  • Handling that redirects errors back to the original form and correlates errors to the fields with which they are associated.
  • Advanced error handling using the controller's lifecycle.

Client-side validation is autogenerated, and is discussed in more detail in the chapter on Views. The other handling mechanisms are discussed below.

Basic Handling

A controller method is allowed to throw any exception:

public void controllerMethod() throws Exception {
  // Do operations that may throw exceptions ...
}

Alternately, you can use the controller's handleError() method. This is functionally equivalent to throwing the exception, but gives you a bit more control:

public void controllerMethod() {
  try {
    // Do operations that may throw exceptions ...
  } catch (Exception ex) {
    handleError(ex);
  }
}

Any exception thrown by the controller method (including runtime exceptions) will be caught by the Chrysalis system. Normal processing will terminate, and the error will be forwarded to an error page. The exception object is stored in the HttpServletRequest under the name PageContext.EXCEPTION, as per the JSP standard. This exception can be retrieved in the error page in one of three ways:

  1. Retrieve the exception object directly.

    <%-- In the error JSP page --%>
    <% error = request.getAttribute(PageContext.EXCEPTION); %>
    
  2. Use the JSP error page declaration, which will store the exception in the standard exception implicit object.

    <%-- In the error JSP page --%>
    <%@ page isErrorPage="true" %>
    <%
    if (exception != null) {
      exception.printStackTrace(new java.io.PrintWriter(out));
    }
    %>
    
  3. Use <jutil:printError /> tag, which prints the error message in the page.

    <%-- In the error JSP page --%>
    <jutil:printError />
    

The third option is simplest and preferred option.

Specifying Error Pages

You can define the error page for a controller method at various levels of specificity.

  1. You can specify an error page programatically using the controller's handleError() method. This method is functionally equivalent to throwing the exception from the controller method, except that it allows you to specify an error page.

    public ExampleController extends Controller {
      public void controllerMethod {
        try {
          // Do stuff ...
        } catch (Exception ex) {
          handleError("/error.jsp", ex);
        }
      }
    }
    
  2. You can specify an error page for a controller method in the chrysalis.xml file for the controller package. This example specifies the error page for the editItem() method.

    <!-- In the controller chrysalis.xml -->
    <config>
      <class name="CatalogController">
        <method name="editItem" parameters="item" errorpage="/error.jsp" />
      </class>
      <!-- Other configuration data ... -->
    </config>
    
  3. You can specify a default error page for a whole controller.

    <!-- In the controller chrysalis.xml -->
    <config>
      <class name="CatalogController">
        <default errorpage="/error.jsp" />
        <method name="editItem" parameters="item" />
      </class>
      <!-- Other configuration data ... -->
    </config>
    
  4. You can specify an error page for the entire controller package.

    <!-- In the controller chrysalis.xml -->
    <config>
      <default errorpage="/error.jsp" />
      <class name="CatalogController">
        <method name="editItem" parameters="item" />
      </class>
      <!-- Other configuration data ... -->
    </config>
    
  5. You can specify a global default error page in the ChrysalisConfig.xml file.

    <config>
      <controller>
        <package>com.domain.catalog.controllers</package>
        <default view="/index.jsp" errorpage="/error.jsp" />
      </controller>
    </config>
    
  6. You can specify no error pages anywhere, in which case Chrysalis will default to the error page /error.jsp.

Chrysalis uses the most specific error page defined. The transfer between the controller and the error page will be a forwarding operation [RequestDispatcher.forward()].

The error page should always be specified with the .jsp extension, even in Servlet 2.2. This is because all error processing happens on the server-side, and does not pass through the view filter. Error page location is relative to the web-application root (so that it omits the context path).

The REFERER_PAGE Error Page

There is one special value that can be assigned to an error page.

<!-- In the controller chrysalis.xml -->
<config>
  <class name="CatalogController">
    <method name="editItem" parameters="item" errorpage="REFERER_PAGE" />
  </class>
  <!-- Other configuration data ... -->
</config>

If the error page is designated the "REFERER_PAGE", Chrysalis redirects error messages back the original page. It includes all the original request parameters used to invoke the original page, so that the page will render correctly.

Unlike normal error handling, the redirection to the REFERER_PAGE is a client-side redirect [response.sendRedirect()]. This means that the browser URL will be accurate for the page displayed. Error information is temporarily cached in the session during the redirect operation, but is ultimately stored in the PageContext.EXCEPTION request attribute, as describe above.

If an error is redirected back to a form rendered using the Chrysalis custom tags, the tags will indicate errors as follows.

  • Errors associated with individual form fields will be displayed in red above the field tag [<jhtml:field />] for that field.
  • The last data entry values will be used to populate the form fields instead of the current values in the business object.
  • Global errors can be displayed using the <jutil:printError /> tag.
<jutil:printError/>

Errors and the Controller Lifecycle

More advanced initialization, error handling and release logic for a controller can be managed using the controller start() and stop() lifecycle methods, as described below.

Controller Lifecycle

Simple controller invocations work only with the controller method (and initializer for complex method parameters, if necessary). More advanced applications may need a more sophisticated lifecycles. The Controller has several lifecycle methods that will be invoked before and after any controller method. The full lifecycle for a controller method invocation is as follows:

  1. start()
  2. Initializer (getter) methods for complex (JavaBean) parameters
  3. The controller method
  4. stop()

The start() and stop() methods can be used for global initialization and cleanup for controllers. For example, if every controller method uses a JDBC connection, it can be opened in the start() method and released in stop().

import java.sql.*;
import org.chwf.servlet.Controller;

public class JDBC_Controller extends Controller {
  
  private static final String URL = "[JDBC-URL]";
  private static final String USER = "[USER ID]";
  private static final String PASSWORD = "[PASSWORD]";
  
  Connection con = null;
  
  public void start() throws SQLException {
    con = DriverManager.getConnection(URL, USER, PASSWORD);
  }
  
  // Controller methods ...
  
  public void stop() throws SQLException {
    if (con != null) {
      con.close();
      con = null;
    }
  }
}

The stop() method is always invoked, even if there are errors thrown by other lifecycle methods; this method can be used like a finally block. Bear in mind that the failure may occur in the start() method, so be sure to test for null values for resources in the stop() method, as indicated above.

Because of the way the start() and stop() methods are invoked, they can also be used to demarcate transactions.

public void start() throws SQLException {
  con = DriverManager.getConnection(URL, USER, PASSWORD);
  con.setAutoCommit(false);
}

// Controller methods ...

public void stop() throws SQLException {
  if (con != null) {
    try {
      if (this.hasError()) {
        con.rollback();
      } else {
        con.commit();
      }
    } finally {
      con.close();
      con = null;
    }
  }
}

These examples use JDBC, but the logic works just as well for other kinds of transactional resources, including JTA, JDO, JMS and so forth.

Note that the start() and stop() methods will be called before and after every controller method invocation, including the <jutil:use> tag invocations in JSP pages. You should only put logic into these lifecycle methods that is truly global to the entire controller.

Of course, the best way to handle this is to organize your controller logic into classes based on common start() and stop() logic. You can even define generic controller superclasses, like JDBCController or JMSController, for controllers that interact with a particular kind of resource.

You can also do more sophisticated resource management using resource factories.

Chrysalis Security

Chrysalis supports security at two levels:

  • Controller Security: Pass/fail security for controller invocations.
  • Page Security: Security for view pages that redirects to a login page (and possibly switching to SSL).

Controller Security

By default, Chrysalis controllers do not require the user to be logged into the system. If you wish to constrain access to particular controller methods, you can do so by defining a "role" attribute for that method:

<config>
  <class name="CatalogController">
    <method name="editItem" parameters="item" role="editor" />
  </class>
  <!-- Other configuration data ... -->
</config>

If a user tries to invoke a secured controller method without (a) being logged in and (b) belonging to the specified role, Chrysalis throws an exception. Chrysalis has a special "AUTHENTICATED" role, of which all users are considered a member. If you want a controller method to be accessible to anyone who is logged in, assign it the "AUTHENTICATED" role.

The default Chrysalis installation uses the J2EE standard web security mechanisms to authenticate users. You must specify the authentication mechanism and set basic security constraints in the web.xml deployment descriptor as per the Servlet standard.

If you wish to use another, custom security mechanism, you can define a custom org.chwf.plugin.User plug-in class, as described in the JavaDoc documentation for that class.

Page Security

View pages can also be secured in Chrysalis, in the page configuration chrysalis.xml file.

<config>
  <page name="showAccounts">
    <security role="account-manager" encryption="ALWAYS" login="/login.jsp" />
  </page>
  <page name="login">
    <security encryption="ALWAYS" />
  </page>
  <!-- Other configuration data ... -->
</config>

In addition to roles, a page can also be assigned a login page. Chrysalis will redirect to the login page if the user is not authenticated. If the user is authenticated and belongs to the role, Chrysalis allows access to the page. If the user is authenticated, but does not belong to the role (or if there is no login page), Chrysalis generates an error message.

Finally, Chrysalis can specify encryption requirements for view pages:

  • ALWAYS: Chrysalis redirects to the same page, switching to https:// (SSL encryption).
  • AS_REQUESTED: Chrysalis makes no changes.
  • NEVER: Chrysalis redirects to the same page, switching to http:// (no SSL encryption).

The final option may seem strange, but is useful for commercial sites where a few pages need to be secure (making a purchase) but the rest should not be (general catalog browsing).

The login page itself should (a) not require any roles and (b) ALWAYS have encryption. You can relax the second restriction if you do not care about userid and passwords being intercepted, but if that is the case, you might want to reconsider having security at all.

Page security is primarily useful for applications that have custom security. Applications that use J2EE standard security can use the security configuration in the web.xml to achieve the same effects.

Chrysalis Plugins

Chrysalis supports a number of plugins to custom its behavior. These plugins must be subclasses of the abstract plugin classes defined in the org.chwf.plugin package. Custom plugins are assigned in the ChrysalisConfig.xml file:

<plugin>
  <user class="Custom-Class-Name" />
  <logger class="Custom-Class-Name" debug="true" />
  <optionLister class="Custom-Class-Name" />
  <scripter class="Custom-Class-Name" />
</plugin>

If no custom plugins are defined, Chrysalis uses the default plugins from the org.chwf.plugin.defaults package.