A Brief Exposé of
Seam Catch

Russell Bateman
31 May 2011
last update:

Bruce Eckel is sort of a personal guru of mine, not that I worship him, but I do take everything he says seriously if and when I go to the trouble of seeking his advice.

"[W]hen projects get large (...), checked exceptions get ungainly and seem to cause problems"...

..because the phenomenon of the checked exception results in coders seeking to make it go away (for all sorts of reasons).

I've just put words in his mouth. Sorry.

Nevertheless, this is my argument in favor of making them go away (by handling them via Seam Catch). I see it as a "catch-all" capability that does not prohibit explicitly catching and dealing with semantically relevant exceptions in the code at hand which I certainly want to do. Actually, it's probably a lot richer as I'll discover soon when I begin to rely upon it in an exciting new project.

And, if C# and Python were any argument, this is just a way of moving Java closer to those languages in which the original assumption (i.e.: that forcing checked exceptions is a good thing) was rejected.

Introduction

Seam Catch provides a simple, lightweight infrastructure based on Java 6 contexts and dependency injection (CDI) that helps a developer establish a unified and robust exception handling process.

Think of Seam Catch as catching and processing any and all exceptions as you direct in your code. Throwable is the base exception class in Java. It represents all exceptions; all are extensions of it.

How it works

When an exception is thrown, it's likely wrapped in another. The collection of exceptions in its entirety is called an exception stack trace. The outermost isn't usually the most important (or, it's misleading). Hence, Seam Catch doesn't merely pass the trace directly to the exception handlers (that you code and mark with @Handles). Instead, it unwraps the stack trace and treats the root exception as the primary one.

So, for example, Seam Catch first handles, a BatchUpdateException or a DataTruncation, rather than the more vague SqlException that will wrap them.

It calls any handler designated as catching the most significant exception, then works its way through the stack trace notifying designated handlers until a handlers marks the exception as "handled". A handler might instead tell Seam Catch to rethrow the exception outside the Seam Catch infrastructure, a which point exception handling returns to the grosser Java exception handling framework. For more on this, see Seam Catch documentation, sections 3.3. Exception stack trace processing and 3.4. Exception handler ordering.

Seam Catch classes

There are two:

  1. CaughtException
  2. ExceptionStack

CaughtException has several methods that aid in flow control. Their names identify their function well, I think:

  1. abort()
  2. rethrow()
  3. handled()
  4. proceed
  5. proceedToCause()

ExceptionStack contains information about the exception causes relative to the current exception and is the source of the execution classes the invoked handlers are matched against. Call getExceptionStack() on the CaughtException object.

More on this is covered in 3.5. APIs for exception information and flow control.

Intervention

Seam Catch can not only catch checked events, it can intervene in how they are handled including...

Seam Catch is simple...

...because the interface is annotative and minimal.

The annotations

@Observes
void executeHandlers( @Observes @Any ExceptionToCatch payload, final BeanManager bm )
{
   ...
}
@Inject

(Don't put too much stock or significance in this example. I don't grok it well enough to explain it yet.)

public class MyBaseApplication
{
   @Inject
   private Event< ExceptionToCatch > catchEvent;

   void service()
   {
      try
      {
         ...
      }
      catch( MyBaseApplicationException e )
      {
         // adding own basic integration...
         this.catchEvent.fire( new ExceptionToCatch( e ) );
      }
   }
}
@HandlesExceptions, @Handles

See 3.2.2. @Handles for a step-by-step on how the standard exception handler(s) work.

/**
 * Here we implement all the standard and back-up extension handlers. These have utter flexibility
 * not only to handle the exception, but to decide in what order, whether to pass it on to another
 * handler, rewrite the trace, etc.
 */
@HandlesExceptions
public class MyStandardExceptionHandlers
{
   public void rollbackTransaction( @Handles CaughtException< PersistenceException > event, UserTransaction tx )
   {
      try
      {
         tx.rollback();
      }
      catch( SystemException e )
      {
         ...
      }

      event.handled();
   }

   public void logExceptions( @Handles( during     = TraversalMode.BREADTH_FIRST,
                                precedence = Precedence.HIGH )
                        CaughtException< Throwable > event,
                        Logger                       log )
   {
      log.error( "Exception caught: " + event.getException().getMessages() );
   }

   public void endConversation( @Handles CaughtException< MyBaseApplicationException > event,
                         Logger                                                  conv )
   {
      if( !conv.isTransient() )
         conv.end();
   }
}

Seam Catch is lightweight...

...because it consists of only two JARs, not the entire Seam framework.

Also, however, a JAR from the Java EE 6 Platform is required:

Appendix: Links


Appendix: a snippet of example NavigationServlet

/*
 * JBoss, Home of Professional Open Source Copyright 2011, Red Hat, Inc., and individual
 * contributors by the @authors tag. See the copyright.txt in the distribution for a full listing of
 * individual contributors. 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.jboss.seam.exception.example.basic.servlet.navigation;

import java.io.IOException;

import javax.enterprise.event.Event;
import javax.inject.Inject;
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 org.jboss.seam.exception.control.ExceptionToCatch;

/**
 * Navigation rules based on Seam Servlet events.
 *
 * @author Jason Porter
 */
@WebServlet( name = "NavigationServlet", urlPatterns = "/Navigation/*" )
public class NavigationServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;

    private enum NavigationEnum
    {
        NULLPOINTEREXCEPTION( new NullPointerException( "Null pointer thrown" ) ),
        ASSERTIONERROR      ( new AssertionError( "Assertion Error" ) ),
        IOEXCEPTION         ( new IOException( "IOException" ) ),
        WRAPPEDILLEGALARG   ( new IllegalStateException( "Wrapping IllegalStateException",
                              new IllegalArgumentException( "Inner IAE" ) ) ),
        INDEX_JSP           ( new NullPointerException( "index.jsp" ) )
//      INDEX_JSP           ( new IllegalStateException( "Wrapping IllegalStateException",
//                            new IllegalArgumentException( "index.jsp" ) )
        ;
        private final Throwable    exception;

        private NavigationEnum( final Throwable e ) { this.exception = e; }
        public Throwable getException() { return exception; }
    }

    @Inject
    private Event< ExceptionToCatch > catchEvent;

    @Override
    protected void service( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
    {
        final String uri = req.getRequestURI();

        try                            // http://localhost:9090/NavigationServlet/Navigation/index.jsp
        {
            String                desc = uri.substring( uri.lastIndexOf( "/" ) + 1 )
                                            .toUpperCase()
                                            .replace( '.', '_' );
            final NavigationEnum  nav  = NavigationEnum.valueOf( desc );

            throw new ServletException( nav.getException() );   // wrapping because we don't want to
                                                         // try / catch and we can't add
                                                         // the throws list
        }
        catch( IllegalArgumentException e )
        {
            this.catchEvent.fire( new ExceptionToCatch( e ) );  // If there is no catch integration,
                                                         // this is how to add basic
                                                         // integration yourself
        }
    }
}