Announcement Announcement Module
Collapse
No announcement yet.
<exception-handler> in SWF1.0.5 drops stack trace of original exception??? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • <exception-handler> in SWF1.0.5 drops stack trace of original exception???

    I am using SWF 1.0.5 on a large critical project that is near completion and upgrading to 2.0 is, unfortunately, not feasible (I wish it were backwards-compatible.) I have implemented a very well-working error-handling strategy (almost!) using SWF's <exception-handler>. Each of my flows has the following definition:

    Code:
    <exception-handler bean="flowErrorHandler"/>
    The "flowErrorHandler" bean is defined as follows:

    Code:
    <bean id="flowErrorHandler" class="com.xyz.myapp.web.webflow.FlowExceptionHandler">
            <property name="targetStates">
                <map>
                    <entry key="com.xyz.myapp.web.BusinessException1" value="targetState1"/>
                    <entry key="com.xyz.myapp.web.BackNavigationException" value="expiredMsgState" />
                    <entry key="java.lang.Throwable" value="systemerror"/>
                </map>
            </property>
        </bean>
    My FlowExceptionHandler extends TransitionExecutingStateExceptionHandler adds the ability to inject the target states via a setter and some custom processing:

    Code:
    import com.xyz.common.config.InvalidConfigurationException;
    import org.apache.log4j.Logger;
    import org.springframework.webflow.engine.RequestControlContext;
    import org.springframework.webflow.engine.support.TransitionExecutingStateExceptionHandler;
    import org.springframework.webflow.execution.FlowExecutionException;
    import org.springframework.webflow.execution.ViewSelection;
    
    import java.util.Map;
    import java.text.MessageFormat;
    
    public class FlowExceptionHandler extends TransitionExecutingStateExceptionHandler {
        private static final String MSG_ERR_EXCEPTION_CLASS =
                "Error configuring FlowExceptionHandler: ''{0}'' is not a recognized exception class.";
        private static final String MSG_REGISTERING_HANDLER =
                "Registered error handler for ''{0}'': on error will forward to stateId=''{1}''.";
        private static final String MSG_ERROR_INFO =
                "{0} was caught and handled by global flow error handler in flow ''{1}'', state ''{2}''. Proceeding to designated state ''{3}''.";
        protected static final Logger logger = Logger.getLogger(FlowExceptionHandler.class);
    
        /**
         * Handles the incoming flow exception and redirects the flow to the designated state associated with this exception
         * type. The exception-type-to-state mappings must be loaded via the {@link #setTargetStates} method.
         *
         * @param e       the FlowExecutionException object generated by the WF engine that wraps the original exception
         * @param context context of the current client request
         * @return signaled event to proceed to the designated view that is determined by the configuration of this handler
         */
        @Override
        public ViewSelection handle(FlowExecutionException e, RequestControlContext context) {
            logger.error("Some message", e); // want to log the complete stack trace!!!
            
            // do custom processing here...
            return super.handle(e, context);
        }
    
        /**
         * Checks whether this handler should handle the incoming exception. Overrides the default behavior on the SWF
         * (v1.0.5) framework's class to avoid the infinite loop in the case of the {@link #handle} method
         * throwing an IllegalArgumentException due to a handler being incorrectly configured to forward to a non-existing
         * state as well as to handle any Throwable or Exception.
         *
         * @param e the FlowExecutionException object generated by the WF engine that wraps the original exception
         * @return boolean true or false.
         */
        @Override
        public boolean handles(FlowExecutionException e) {
            return e.getCause() != null && !(e.getCause() instanceof IllegalArgumentException) && super.handles(e);
        }
    
        /**
         * Sets the mappings between the exception types (class names) and target flow states to which control must be
         * directed after the exception is handled. This <tt>set</tt> method is introduced to allow <i>setter-based
         * injection</i> of the mappings into this handler, and internally uses the {@link #add} method on the superclass to
         * insert the mappings.
         *
         * @param states map of key/value pairs where each key is an exception class name and each value is a flow state ID
         * @throws InvalidConfigurationException if invalid exception to state configuration is being loaded, e.g. one of
         *                                       the specified exception classes is not recognized, etc.
         */
        public void setTargetStates(Map<String, String> states) throws InvalidConfigurationException {
            for (String className : states.keySet()) {
                try {
                    String stateId = states.get(className);
                    add(Class.forName(className), stateId);
                    logger.debug(MessageFormat.format(MSG_REGISTERING_HANDLER, className, stateId));
                }
                catch (ClassNotFoundException ex) {
                    throw new InvalidConfigurationException(MessageFormat.format(MSG_ERR_EXCEPTION_CLASS, className), ex);
                }
            }
        }
    }
    All my exceptions within the application are runtime exceptions, of course, which allows them to freely propagate to the designated handlers without being altered or mishandled. Everything works beautifully, except for one thing: the FlowExecutionException object created by the webflow engine does not have the stack trace of the cause, i.e the actual application exception. Was this done on purpose? I doubt that such a thing (chopping off the stack trace) could be deliberately done by such expert programmers as the WF authors. No good framework developer will ever assume that they could replace - not complement! - the original complete history of an application-specific error with their own generic one-liner? I see bad programmers do that every day, but not the Spring guys! What am I missing? Must be something obvious.

    If I disable the <exception-handler> thing and let the exceptions propagate to my global Spring MVC exception resolver, everything is fine, the stack trace is not lost. But using <exception-handler> gives me FlowExecutionException that just doesn't contain the complete error information, which is unbelievable, and unacceptable to me. If it's a bug, I am sure it is fixed in 2.0, but is there a way to make it work in 1.0.5? I would really love to make it work b/c it is such a slick way to implement a one-stop error-handling solution.

    Thanks for any advice. Please help!!!
    Last edited by constv; May 24th, 2008, 10:50 AM.

  • #2
    Actually, everything seems to works fine now. I still can only guess why it was happenig before, but getCause().getStackTrace() (on the incoming FlowExecutionexceptio object) would consistently return null. I had discovered that I had an old definition of a test exception handler elsewhere in my spring context files. That class was extending my FlowExceptionHandler (not altering the exceptions though), and it was configured to handle the same exception types. Quite possibly, it was messing things up. Everything works great right now after I found and removed the duplicate <exception-handler> definition. I thought I just would make the record straight.

    Comment


    • #3
      Spoke too soon. Stack trace disappears again!

      Now I am totally confused! For a moment there I thought it was working... I am setting a breakpoint in the handles() method on my class that extends TransitionExecutingStateExceptionHandler (see the code above.) The FlowExecutionException object that is passed to the method contains a non-null cause, including the nested causes, but... all stack traces (for each and every cause exception object) are null!

      If "e" is the FlowExecutionException object passed to the handles() method, Calling e.getCause() and this.findRootCause(e), as expected, returns the same non-null cause object. However, this.findRootCause(e).getStackTrace() returns null! Does anyone have any idea what may be causing it? Note that all the proper nested causes themselves are not null. What could possibly be altering the original exception. The original exception is always a runtime exception thrown by one of my services that basically wraps any underlying exception within the service method, adding a clarifying message, or any uncaught 3rd-party RTE. For example:

      Code:
      public void sendEmail(...)
      try {
         // sends confirmation email 
      } catch (Throwable t) { 
           // all we need to know is that this operation failed: wrap into RTE, 
           // add msg and propagate to designated handler configured to handle this error specifically
           // never worry about this exception anywhere else
           throw new SendEmailException("some msg", t);
      }
      The SendEmailException extends RuntimeException and its constructor looks like this:
      Code:
          public SendEmailException (String msg, Throwable cause) {
              super(msg, cause);  // note: we are not discarding the cause!
          }
      As soon as this exception is thrown, it gets correctly redirected by the Webflow engine to my error handler as I described in the original posting. In the case of this example, the configuration map entry in the exception handler definition would look like this:

      Code:
      <entry key="com.xyz.myapp.web.SendEmailException" value="myNextSuccessState"/>
      The handler would process the error (log it, save some info in the database for later re-sending of the email), and direct the flow to the next state as if nothing bad happened (since in this particular case, we don't want to interrupt the order process and scare off the user.) The webflow engine packages my exception inside a FlowExecutionException object and passes it to my implementation of TransitionExecutingStateExceptionHandler. Everything works as expected, except for one thing. As that FlowExecutionException exception arrives in the handles() method that checks whether it should be handled by this class, the exception is missing the stack trace and stack traces for all nested causes.

      Can anyone from the SWF team please shed some light on this? Am I doing something wrong somewhere?

      Many thanks in advance!
      Constantine
      Last edited by constv; May 20th, 2008, 01:25 PM.

      Comment


      • #4
        In case anyone stumbles upon this thread, I just want to clear any confusion: the example above actually does work fine. There must have been some kind of problem with my build that caused the IntelliJ debugger to show null stack traces. Logging the errors as shown in the examples properly logs the complete stack traces, but I wasn't seeing it because... I finally noticed that I was using logger.error(e), i.e. logger.error(String) - instead of logger.error(String, Throwable)... D'oh! :-) No wonder it was logging the e.toString() output which is basically the detail message w/o stack trace. So, I made an ..ss of myself! )))


        Hope this example helps someone.
        Cheers,
        CV
        Last edited by constv; May 24th, 2008, 10:59 AM.

        Comment


        • #5
          Nice post Constantine.

          Some hints for people using your code with SWF2.x:

          Class name change:
          Code:
          TransitionExecutingStateExceptionHandler
          changed to
          Code:
          TransitionExecutingFlowExecutionExceptionHandler.
          Method signature changes:
          Code:
          ViewSelection handle(FlowExecutionException e, RequestControlContext context)
          changed to
          Code:
          void	handle(FlowExecutionException exception, RequestControlContext context)
          and

          Code:
          public boolean handles(FlowExecutionException e)
          changed to
          Code:
          boolean canHandle(FlowExecutionException e)
          - Peter

          Comment


          • #6
            Should the FlowExceptionHandler class extend TransitionExecutingFlowExecutionExceptionHandler or implement FlowExecutionExceptionHandler?

            Comment


            • #7
              Originally posted by kmsheph View Post
              Should the FlowExceptionHandler class extend TransitionExecutingFlowExecutionExceptionHandler or implement FlowExecutionExceptionHandler?
              You want your custom handler to reuse the functionality that is already provided by the framework in TransitionExecutingFlowExecutionExceptionHandler. So, yes, you want to extend the existing framework class instead of implementing the interface and doing everything from scratch.

              Comment


              • #8
                Originally posted by constv View Post
                You want your custom handler to reuse the functionality that is already provided by the framework in TransitionExecutingFlowExecutionExceptionHandler. So, yes, you want to extend the existing framework class instead of implementing the interface and doing everything from scratch.
                The reason I asked is because I found the following posted by Jeremy:
                http://forum.springframework.org/sho...97&postcount=7

                Comment


                • #9
                  In order for my custom handler to work I had to implement FlowExecutionExceptionHandler as described in the following thread:
                  http://forum.springframework.org/sho...397#post183397

                  Comment


                  • #10
                    Originally posted by kmsheph View Post
                    In order for my custom handler to work I had to implement FlowExecutionExceptionHandler as described in the following thread:
                    http://forum.springframework.org/sho...397#post183397
                    There are certainly more than just one way. The thread you are referring to seems to be dedicated to JSF. The solution in this thread demonstrates how you can easily map various exception types to different views in an SWF-Spring MVC application - via external configuration (Spring XML config.) The idea is that you should normally have just a few specific cases that require special handling such as displaying views that are not the standard error page. All other exceptions must result in the standard error page view. To have your error pages display custom (use-case-specific) messages you can resolve and place the appropriate message key into the request context - as part of the exception handling. The JSP can have display the text retrieved from the message resource based on that key (e.g. using the fmt:message JSTL tag.) You may want to check out one of the related discussions on exception handling here:

                    http://forum.springframework.org/sho...t=63549&page=2

                    And, sure, it's not necessary to extend the default resolver implementation (which I think does very little anyway) if you are providing your own.

                    Comment

                    Working...
                    X