Ajax on Java

9.5. Writing the JSF Support for Ajax

When an Ajax request arrives, it must move through the JSF lifecycle. At the appropriate phase in the lifecycle, the request must be handed off to a service for processing. In JSF, a phase listener is responsible for handing requests to services at the appropriate point in the lifecycle. It listens for phase events in the lifecycle and, when an appropriate event arrives, calls the service.

A phase listener is a class that implements the PhaseListener interface, which requires beforePhase( ) and afterPhase( ) methods. The JSF framework calls these methods before and after each phase change. We'll use this mechanism to implement a ZipCodePhaseListener that executes the Ajax backend support code (Example 9-10).

Example 9-10. The ZipCodePhaseListener class

public class ZipCodePhaseListener implements PhaseListener { public void afterPhase(PhaseEvent event) { String viewId = event.getFacesContext().getViewRoot().getViewId( ); if (viewId.indexOf("Ajax") != -1) { handleAjaxRequest(event); } } private void handleAjaxRequest(PhaseEvent event) { FacesContext context = event.getFacesContext( ); HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse( ); Object object = context.getExternalContext().getRequest( ); if (!(object instanceof HttpServletRequest)) { // only handle HttpServletRequests return; } HttpServletRequest request = (HttpServletRequest) object; String zipcode = request.getParameter("zip"); Location location = ZipcodeManager.getZipcode(zipcode); // actually render using XML StringBuffer returnXML = null; returnXML = new StringBuffer("\r\n<location>"); returnXML.append("\r\n<city>"+ location.getCity( )+"</city>"); returnXML.append("\r\n<state>"+ location.getState( )+"</state>"); returnXML.append("\r\n</location>"); response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); try { response.getWriter().write(returnXML.toString( )); event.getFacesContext().responseComplete( ); } catch (IOException e) { e.printStackTrace( ); } } public void beforePhase(PhaseEvent arg0) { //not used, but implemented to satisfy compiler } public PhaseId getPhaseId( ) { return PhaseId.RESTORE_VIEW; } }

Everything needed to process the request is encapsulated in the PhaseEvent. We don't care about the beforePhase( ) method, so it can have an empty body. The afterPhase( ) method looks at the PhaseEvent to determine when it needs to process an Ajax request. afterPhase( ) is called after each request phase, and if the string "Ajax" is found in the viewId, we know that we're processing an Ajax request and that we need to pass it off to an appropriate service.

The viewId is obtained by a call to getViewId( ), which returns the id of the view that sent the last request. To verify whether the request was an Ajax request, we execute a check for the string "Ajax" in the viewId:

if (viewId.indexOf("Ajax") != -1) {

For example, look at the URL in the retrieveCityState( ) function of Example 9-8. The URL is ZipCode-Ajax.faces, and that is the string that the viewId contains. Thus, by checking the origin of the request, which is stored in the viewId, this JSF application is able to determine that this is an Ajax request that needs to be serviced.

This strategy for determining whether a request is an Ajax request requires that the developer use URLs containing the string "Ajax" only for Ajax requests. If the developer creates a normal JSF page whose name contains "Ajax," this will cause a problem: each time that page sends a request, the application will try to handle the request as an Ajax request.

When there's an Ajax request to be processed, afterPhase( ) passes control to the handleAjaxRequest( ) method. handleAjaxRequest( ) gets a FacesContext object from the event and then gets a ServletResponse from the context. With the ServletResponse in hand, handleAjaxRequest( ) calls ZipcodeManager.getZipcode( ) to look up the city and state for the zip code; it then uses this data to build the XML response to send back to the client. Sending the response back to the client is just a matter of using the ServletResponse object to set a MIME type and any appropriate headers and then using a Writer to send the XML back to the browser.

ZipcodeManager (Example 9-11) is a utility class that looks up the state and city corresponding to a given zip code. It's a simple database lookupnothing particularly interesting, and certainly nothing that you couldn't improve on in a more sophisticated application.

Example 9-11. The ZipcodeManager class

public class ZipcodeManager { static public Location getZipcode(String zip) { Location location = null; Connection con = DatabaseConnector.getConnection( ); String sqlString = ""; location = new Location( ); location.setZipCode(zip); // put in original zip code try { sqlString = "SELECT CITY,STATE,ZIPCODE FROM ZIPCODES WHERE ZIPCODE='"+zip+"';"; Statement select = con.createStatement( ); ResultSet result = select.executeQuery(sqlString); if (result.next( )) { // process results one row at a time location.setCity(result.getString(1)); location.setState(result.getString(2)); location.setZipCode(result.getString(3)); } } catch (Exception e) { System.out.println("exception in login"+e.getMessage( )); } finally { if (con != null) { try { con.close( ); } catch (SQLException e) { } } } return location; } }

To pass the state and city back to the caller, we use a simple Java bean class called Location. This class is shown in Example 9-12.

Example 9-12. The Java bean Location.java

public class Location { private String city; private String state; private String zipCode; public String getCity( ) { return city; } public void setCity(String city) { this.city = city; } public String getState( ) { return state; } public void setState(String state) { this.state = state; } public String getZipCode( ) { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } }

Категории