`

How to add exception handling to JSF applications

    博客分类:
  • JSF
阅读更多

原文地址:http://www.openlogic.com/wazi/bid/259014/How-to-add-exception-handling-to-JSF-applications

 

Everyone who codes Java EE web applications needs to pay attention to exception handling. When a program encounters an error, developers can display friendly messages for end users, which increases their trust in the application. Also, by adding adequate exception handling, you can troubleshoot and debug application defects. Since version 2.0 the JavaServer Faces framework has supported an exception handling mechanism to provide a centralized place for handling exceptions in JSF applications. Here's how you can utilize it to write professional applications.

A good example to illustrate everything that goes into proper exception handling is the guessNumber application, in which the application generates a random number within a specific range, and the user is asked to enter a number in this range. After the user enters a valid number, the application can provide three possible responses:

  1. If the entered number is equal to the generated number, the application congratulates the user and asks if he wants to try again.
  2. If the entered number is less than the generated number, the application asks the user to enter a number greater than the entered number.
  3. If the entered number is greater than the generated number, the application asks the user to enter a number less than the entered number.

To code the guessNumber application, we can use three pages:

  1. input.xhtml, in which the user enters a number.
  2. congratulations.xhtml, which displays the congratulations message if the user succeeds in guessing the correct number.
  3. error.xhtml, which is displayed if the application has an internal error.

The following code snippet shows the input.xhtml page:

 

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

<h:head>
  <title>#{bundle['guessNumber.input_page.title']}</title>
</h:head>

<h:body>
    <h:form>
        <h:panelGrid columns="3">
            <h:outputText value="#{bundle['guessNumber.input_number']}"/>
            <h:inputText id="inputNumber" value="#{randomNumber.number}" required="true">
                <f:validateLongRange minimum="0" maximum="10"/>
            </h:inputText>
            <h:message for="inputNumber"/>
        </h:panelGrid>

        <h:commandButton value="#{bundle['guessNumber.guess_now']}" action="#{randomNumber.guessNumber}"/>        
        <h:messages />
    </h:form>
</h:body>
</html>

 

Here a form contains an input text ("inputNumber") in which the user enters a number, and a command button ("Guess Now") that the user clicks in order to send the guess. The input text is required and is validated to be in the range from 0 to 10. When the command button is clicked, #{randomNumber.guessNumber} (the guessNumber action method of the RandomNumber managed bean) is executed. The code below shows the RandomNumber managed bean.

 

package com.wazi.guessnumber.model;

import java.io.Serializable;
import java.util.ResourceBundle;
import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

@ManagedBean
@SessionScoped
public class RandomNumber implements Serializable {
    private Integer number;
    private Integer generatedNumber;
    
    public String guessNumber() {          
        if (generatedNumber == number) {
            return "congratulations?faces-redirect=true";
        } 
        
        FacesContext context = FacesContext.getCurrentInstance();            
        ResourceBundle bundle = ResourceBundle.getBundle("com.wazi.guessnumber.messages", context.getViewRoot().getLocale());

        String tip = "";

        if (generatedNumber > number) {
            tip = bundle.getString("guessNumber.use_bigger_number");
        } else {
            tip = bundle.getString("guessNumber.use_smaller_number");
        }

        context.addMessage(null, new FacesMessage(tip));        
        return null;
    }
    
    public String reset() {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
        
        session.invalidate();
        
        return "input?faces-redirect=true";
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
    
    @PostConstruct
    public void initialize() {
        generatedNumber = (int) (Math.random() * 10);        
        
        System.out.println ("Initializing random generated number: " + generatedNumber);        
    }
}

 

The RandomNumber class is marked as a managed bean using the JSF @ManagedBean annotation and is set in the session scope using the @SessionScoped annotation. Using the @PostConstruct annotation, the initialize() method of the RandomNumber managed bean is called after the managed bean class is instantiated in order to initialize the managed bean. In the initialize() method, the generatedNumber property is set to a random number from 0 to 10.

In the guessNumber() action method, if the user-entered a number is equal to the generatedNumber property, the user is redirected to the congratulations.xhtml page. If the entered number is less than or greater than the generatedNumber property, the user is advised to enter a number that is less than or greater than the entered number.

You can add messages to be displayed by the <h:messages> tag from the JSF action methods using the FacesContext.getCurrentInstance().addMessage() API, specifying two parameters. The first parameter represents the client ID with which this message is associated (if no client ID is available you can set this parameter to null) and the second represents the FacesMessage object.

In the reset() action method, the user session is invalidated and the user is redirected to the input.xhtml page. This method is called in the congratulations.xhtml page when the user clicks on the ("Try again") command button. The following code shows the congratulations.xhtml page:

 

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

<h:head>
  <link href="#{request.contextPath}/css/styles.css" rel="stylesheet" type="text/css"/>      
  <title>#{bundle['guessNumber.cong_page.title']}</title>
</h:head>

<h:body>
    <h:form>
        <h:outputFormat value="#{bundle['guessNumber.correct_input']}" class="highlighted infoMessage">            
            <f:param value="#{randomNumber.number}"/>
        </h:outputFormat>
        <br/><br/>
        <h:commandButton value="#{bundle['guessNumber.try_again']}" action="#{randomNumber.reset}"/>       
    </h:form>
</h:body>
</html>

 

In the congratulations.xhtml page, the congratulations message is displayed and the user has the option to try again by clicking on the ("Try Again") button, which calls the reset method of the RandomNumber managed bean.

Applying the JSF exception-handling mechanism

Now we can apply the centralized JSF exception-handling mechanism on the guessNumber application. After we do so, the application will be able to handle different exceptions in a centralized place and display the exception information in an error page. In order to create a custom exception handler in JSF applications we need to do three things:

  1. Create a custom exception handler class that handles the application exceptions. This handler class should extend an exception handling wrapper class (such as the ExceptionHandlerWrapper class).
  2. Create a custom exception handler factory class that is responsible for creating the instances of the exception handler class. The custom exception handler class should extend the JSF ExceptionHandlerFactory class.
  3. Finally, register the custom exception handler factory class in the faces-config.xml file.

The following code shows the CustomExceptionHandler class, which extends the ExceptionHandlerWrapper class:

 

package com.wazi.guessnumber.exceptions;

import java.util.Iterator;
import javax.faces.FacesException;
import javax.faces.application.NavigationHandler;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;

public class CustomExceptionHandler extends ExceptionHandlerWrapper {
  private ExceptionHandler wrapped; 
  
  public CustomExceptionHandler(ExceptionHandler wrapped) {
    this.wrapped = wrapped;
  }
 
  @Override
  public ExceptionHandler getWrapped() {
    return wrapped;
  }

  @Override
  public void handle() throws FacesException {
    Iterator iterator = getUnhandledExceptionQueuedEvents().iterator();
    
    while (iterator.hasNext()) {
      ExceptionQueuedEvent event = (ExceptionQueuedEvent) iterator.next();
      ExceptionQueuedEventContext context = (ExceptionQueuedEventContext)event.getSource();
 
      Throwable throwable = context.getException();
      
      FacesContext fc = FacesContext.getCurrentInstance();
      
      try {
          Flash flash = fc.getExternalContext().getFlash();
          
          // Put the exception in the flash scope to be displayed in the error page if necessary ...
          flash.put("errorDetails", throwable.getMessage());
          
          System.out.println("the error is put in the flash: " + throwable.getMessage());
          
          NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();          
          navigationHandler.handleNavigation(fc, null, "error?faces-redirect=true");
          
          fc.renderResponse();
      } finally {
          iterator.remove();
      }
    }
    
    // Let the parent handle the rest
    getWrapped().handle();
  }
}

 

The most important method of the CustomExceptionHandler class is the handle() method, which is responsible for handling JSF application exceptions. The getUnhandledExceptionQueuedEvents() method gets all the unhandled exceptions in the JSF application. Every item in the returned Iterable object of this method represents an ExceptionQueuedEvent object. From the ExceptionQueuedEvent object you can get the ExceptionQueuedEventContext object, from which you can retrieve the Throwable object. Using the Throwable object, you can verify the exceptions you want to handle in the applications.

In our custom exception handler, we get the exception message from throwable.getMessage(). It is set to be used in the error.xhtml page in an "errorDetails" attribute that is defined in the flash scope. The flash scope, which was introduced in JSF 2.0, makes objects available only for the next request of the same browser window, which makes it useful if you want to keep information for a short time for the next request only, whether the next request will result from an HTTP redirect or a JSF form postback or simply an HTTP GET for a new page.

The NavigationHandler redirects the response to the application error page (error.xhtml), and the ExceptionQueuedEvent is removed from the Iterable object. The following code shows the error.xhtml page:

 

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">

<h:head>
  <link href="#{request.contextPath}/css/styles.css" rel="stylesheet" type="text/css"/>      
  <title>#{bundle['guessNumber.error_page.title']}</title>
</h:head>

<h:body>
    <div class="highlighted errorMessage">
        <h:outputText escape="false" value="#{bundle['guessNumber.error_page.content']}"/>
    </div>
    <br/><br/>
    <div class="errorDetails">
        Error details: <br/> #{flash.keep.errorDetails}
    </div>
</h:body>
</html>

 

It mainly displays the error details message (which was set from the exception handler class) using #{flash.keep.errorDetails}.

Next, we need to create CustomExceptionHandlerFactory, the custom exception handler factory class that is responsible for creating the instances of the CustomExceptionHandler class:

 

package com.wazi.guessnumber.exceptions;

import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;

public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
 
  private ExceptionHandlerFactory parent;
 
  public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
    this.parent = parent;
  }
 
  @Override
  public ExceptionHandler getExceptionHandler() {
    ExceptionHandler result = new CustomExceptionHandler(parent.getExceptionHandler());
    return result;
  }
}

 

Finally, we need to register the custom exception handler factory class in the faces-config.xml file of the application, as shown below:

 

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="2.1"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd">
    
    <factory>
      <exception-handler-factory>
        com.wazi.guessnumber.exceptions.CustomExceptionHandlerFactory
      </exception-handler-factory>
    </factory>
    
	<!—other configurations here -->
</faces-config>

 

Testing the exception handling mechanism

After setting up this exception handling mechanism in the guessNumber application, if an exception is thrown from the guessNumber application for whatever reason, the error.xhtml page will be displayed. The following screenshot shows the error page if, for example, the guessNumber action method throws a RuntimeException.

Figure 1

<!-- figure1.PNG -->

And here's the error page you see if the guessNumber application throws the common JSF ViewExpiredException, which can result from the user session timeout that causes the JSF not able to restore the view after the JSF form postback.

Figure 1

<!-- figure2.PNG -->

The guessNumber application is a Maven application. You can build it from its POM file using the command mvn clean install, then deploy it on Apache Tomcat or another application server (if you specify the correct dependencies). After deploying the application on a local Tomcat server you should be able to try the application by navigating to http://localhost:8080/guessNumber-1.0/.




This work is licensed under a Creative Commons Attribution 3.0 Unported License

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics