Announcement Announcement Module
Collapse
No announcement yet.
Spring Faces best practice for error handling? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Faces best practice for error handling?

    What is the best practice for error handling when using spring faces?

    When we use plain JSF we call a service method in an action method on a managed bean which returns a String for the next view to render.

    When an exception is thrown in the service layer we handle the exception by adding a message to the facescontext and return null to remain on the same view.

    How can I do this (add a message and remain on the same view) with spring faces since the facescontext is not available in the flow definition.

  • #2
    facesContext is a threadLocal variable and can be obtained any time through the request cycle by calling the appropriate ststic method

    Comment


    • #3
      Originally posted by fawzyj View Post
      facesContext is a threadLocal variable and can be obtained any time through the request cycle by calling the appropriate ststic method
      No actually, to reiterate, Gert is correct in stating that the FacesContext is unavailable during execution of the flow definition (i.e., during action execution).

      That said, the typical approaches to achieving your desired result, Gert, will require a bit of glue code. Essentially you just need to wrap the call to the service layer in another POJO that is able to properly handle the exception. In order to be able to record error messages, you will want to pass that method an instance of org.springframework.binding.message.MessageContext (which is available as an implicit "messageContext" EL variable in the flow definition) and have it return true or false to indicate whether the evaluation succeeds or fails.

      Then you would call that POJO as follows from your flow definition:
      Code:
      <transition on="foo" to="bar">
          <evaluate expression="myPojo.invokeMyService(messageContext)"/>
      </transition>
      In your POJO you can catch any exceptions, add errors to the MessageContext (which our implementation of FacesContext actually uses internally, thus the messages will show up in your JSF view) and return false to indicate that the transition should not continue. Alternately, you could use MultiAction instead of a POJO method evaluation in a similar manner.

      If you've got legacy JSF code that already does what you described and you want to integrate it with minimal change, you do have the option of leaving your action methods as-is and using Web Flow only to handle the navigation portion of handling the event.

      If you want to get creative and try and make things really generic and avoid the glue code so that you can continue to take advantage of the dynamic nature of the flow definition (this would certainly be my preference), you could consider applying an aspect to EvaluateAction.doExecute to catch the right exception types, grab the MessageContext and record errors, then return an error event to keep the transition from continuing. This could actually work quite well as long as what you are trying to catch are meaningful business exceptions where you could just pull out the exception message and record it as an error message.

      We realize that none of the glue code options are completely ideal (though the AOP approach could be quite nice, sometimes you can't make things that generic), and we want to make this sort of thing easier in the future. One idea we've got for the short term is to possibly incorporate dynamic scripting languages inline in your flow definition so you could have more complex logic like this that isn't possible in expression evaluation. Further down the road, we intend to implement flow builders in something more expressive than XML (such as Groovy, perhaps) so as to more naturally allow for such imperative concepts.

      Comment


      • #4
        Jeremy, thanks for your reply!

        We are now at the stage that we are going to implement the AOP approach. If we would apply an aspect to EvaluateAction.doExecute we have a few questions:
        - how do we apply the aspect since we do not declare the EvaluateAction bean ourselves
        - can you give us an example on how to return an error event to keep the transition from continuing

        Comment


        • #5
          Yes, it's a bit advanced which is why I didn't present AOP as the first option.

          In this case you would have to use AspectJ, instead of Spring's AOP proxies, either using load time weaving or compile time weaving (you would weave the aspect into the spring-webflow.jar in this case).

          As for returning a proper error event that will stop the transition, you can keep it as simple as:

          Code:
          return new Event(advisedEvaluateAction, "no", null);
          but I don't particularly like the idea of having the "no" string in there directly. A more future-proof way might be to reuse the ResultObjectBasedEventFactory that EvaluateAction uses indirectly. So in that case the code would look more like:

          Code:
          return new ResultObjectBasedEventFactory.createResultEvent(advisedEvaluateAction, Boolean.FALSE, requestContext);

          Comment


          • #6
            Would it be easier to use an exception-handler in the flow?
            Is it right that we then have to implement the FlowExecutionExceptionHandler and override the canHandle and handle method? And how can we stop transition from continuing in the handle method if an exception occurs (can you give an example)?

            Comment


            • #7
              Yes, actually, now that you mention it, a custom exception-handler would be the better solution. The trick with the exception-handler is that you have to generate your own response. I had forgotten how easy the changes to the API in 2.0 make this to achieve, else I would have recommended this in the first place.

              I wrote a quick prototype to test it out, and my FlowExecutionExceptionHandler implementation goes something like this:

              Code:
              @Component
              public class MyExceptionHandler implements FlowExecutionExceptionHandler {
              
                  public boolean canHandle(FlowExecutionException ex) {
              	if (findBusinessException(ex) != null) {
              	    return true;
              	} else {
              	    return false;
              	}
                  }
              
                  public void handle(FlowExecutionException ex, RequestControlContext context) {
              	context.getMessageContext().addMessage(
              		new MessageBuilder().error().source(null).defaultText(findBusinessException(ex).getMessage()).build());
              
              	Object testState = context.getCurrentState();
              
              	if (testState instanceof ViewState) {
              	    ViewState viewState = (ViewState) testState;
              	    try {
              		viewState.getViewFactory().getView(context).render();
              	    } catch (IOException e) {
              		//Properly handle rendering errors here
              	    }
              	}
              
                  }
              
                  private MyBusinessException findBusinessException(FlowExecutionException ex) {
              	Throwable cause = ex.getCause();
              	while (cause != null) {
              	    if (cause instanceof MyBusinessException) {
              		return (MyBusinessException) cause;
              	    }
              	    cause = cause.getCause();
              	}
              	return null;
                  }
              
              }
              This effectively causes the current view to re-render, and the added error message gets displayed by my <h:messages> component.

              This is a pretty nice approach as this can be registered on a flow level so that you can still use the <evaluate> actions and only need this one custom class to generically handle errors.

              Comment


              • #8
                Thanks a lot!!!

                Comment


                • #9
                  blank page when resuming flow

                  Hi there,

                  we have used the solution Jeremy proposed (ie define a FlowExecutionExceptionHandler that adds the error messages and re-renders the current view). Re-rendering of the page seems to work, but when we try to perform an action (transition) after an error message was handled and the page was re-rendered, we get a blank page. Also no stack trace or anything is available.

                  We have defined the FlowExecutionExceptionHandler in our webContext.xml file
                  <bean id="webflowExceptionHandler" class="be.mypackage.bean.support.WebflowExceptionH andlerBean" >
                  <property name="messageHandler" ref="messageHandler"/>
                  </bean>

                  and configured to use it in our flow.xml file as a general exception handler (apart from any view):
                  <exception-handler bean="webflowExceptionHandler"/>

                  Does anyone know why this is happening and/or what we can do about it?
                  Last edited by Kristof; Jun 3rd, 2008, 08:52 AM.

                  Comment


                  • #10
                    Sorry about that, my solution was slightly flawed. Rendering from the FlowExecutionExceptionHandler like that will leave things in an inconsistent state...in particular, the FacesContext.renderResponse and FacesContext.responseComplete flags do not get properly reset since the example I showed executes render without redirecting (and we actually store those flags in flash scope). A simpler and better approach is to request a flowExecutionRedirect instead of rendering directly.

                    This would change the handle method of my example to look like:

                    Code:
                    public void handle(FlowExecutionException ex, RequestControlContext context) {
                        context.getMessageContext().addMessage(new MessageBuilder().error().source(null).defaultText(findBusinessException(ex).getMessage()).build());
                    
                        context.getExternalContext().requestFlowExecutionRedirect();
                    }

                    Comment


                    • #11
                      Thanks for your quick reply! That really solved it!

                      Comment


                      • #12
                        OMG

                        this is exactly what I need but I just don't undrestand how to implement it :S

                        A little help over here

                        Comment


                        • #13
                          I hope this helps

                          This is how we implemented it:
                          Code:
                          package be.argenta.web.boar.bean.support;
                          
                          import org.springframework.webflow.engine.FlowExecutionExceptionHandler;
                          import org.springframework.webflow.engine.RequestControlContext;
                          import org.springframework.webflow.execution.FlowExecutionException;
                          
                          import be.argenta.boar.exception.ValidationException;
                          
                          public class WebflowExceptionHandlerBean implements
                          		FlowExecutionExceptionHandler {
                          
                          	private MessageHandler messageHandler;
                          
                          	@Override
                          	public boolean canHandle(FlowExecutionException ex) {
                          		return findValidationException(ex) != null;
                          	}
                          
                          	@Override
                          	public void handle(FlowExecutionException ex, RequestControlContext context) {
                          		ValidationException validationException = findValidationException(ex);
                          		messageHandler.addError(context.getMessageContext(), validationException.getMessageKey(),
                          				validationException.getMessageParams());
                          
                          		context.getExternalContext().requestFlowExecutionRedirect();
                          	}
                          
                          	private ValidationException findValidationException(
                          			FlowExecutionException ex) {
                          		Throwable cause = ex.getCause();
                          		while (cause != null) {
                          			if (cause instanceof ValidationException) {
                          				return (ValidationException) cause;
                          			}
                          			cause = cause.getCause();
                          		}
                          		return null;
                          	}
                          
                          	public void setMessageHandler(MessageHandler messageHandler) {
                          		this.messageHandler = messageHandler;
                          	}
                          
                          }

                          Comment


                          • #14
                            Hi, Im trying to implement this solution on my own system but failing to get any satisfactory results.
                            I'm using Eclipse with the Spring snap-in. When I specify the exception-handler tag Eclipse flags it with the message 'Reference Bean x cannot be found'. Although I have mentioned it in several of the config files.

                            If I ignore this error and run the web application within tomcat the application just hangs.

                            BTW. I've new to Spring and Web Flow so any help would be appreciated.

                            Thanks Nick

                            Comment


                            • #15
                              Jeremy,

                              Using the code from this thread, I get the following error:

                              Code:
                              java.lang.IllegalArgumentException: The specified stateId is invalid: state identifiers must be non-blank
                              here is my class:

                              Code:
                              package org.kamerling.utp.exceptions;
                              
                              import org.springframework.binding.message.MessageBuilder;
                              import org.springframework.stereotype.Component;
                              import org.springframework.webflow.engine.FlowExecutionExceptionHandler;
                              import org.springframework.webflow.engine.RequestControlContext;
                              import org.springframework.webflow.execution.FlowExecutionException;
                              
                              @Component
                              public class UtpFlowExecutionExceptionHandler implements FlowExecutionExceptionHandler
                              {
                              
                                  public boolean canHandle(FlowExecutionException ex)
                                  {
                                      if (findBusinessException(ex) != null)
                                      {
                                          return true;
                                      }
                                      else
                                      {
                                          return false;
                                      }
                                  }
                              
                                  public void handle(FlowExecutionException ex, RequestControlContext context)
                                  {
                                      context.getMessageContext().addMessage(new MessageBuilder().error().source(null).defaultText(findBusinessException(ex).getMessage()).build());
                              
                                      context.getExternalContext().requestFlowExecutionRedirect();
                                  }
                              
                                  private BusinessException findBusinessException(FlowExecutionException ex)
                                  {
                                      Throwable cause = ex.getCause();
                                      while (cause != null)
                                      {
                                          if (cause instanceof BusinessException)
                                          {
                                              return (BusinessException) cause;
                                          }
                                          cause = cause.getCause();
                                      }
                                      return null;
                                  }
                              
                              }
                              Any idea why I'm getting an exception?

                              Thanks.

                              Steve

                              Comment

                              Working...
                              X