The AjaxCommand strategy for JSF

As JavaServer Faces continues to mature and its component libraries grow more robust, it becomes an ever more compelling option for enterprise Web application development. Ajax has passed the stage of being the hot new thing to become an expected aspect of competitive Web applications. Marrying the two isn't necessarily difficult, but it does present some interesting puzzles. In this article, Java developer Matthew Tyson tackles one such puzzle: how to handle a single (or similar) Ajax request action across multiple JSF components, without duplicating code in the components.[@more@]

I recently encountered a problem at work that had me delving into the Gang of Four design patterns for a solution. I was working on a tree component that rendered its nodes according to nested facets. The user could delete a node by clicking on a Delete button or by right-clicking and selecting Delete. The delete was to be carried out via an Ajax call.

Coding all this was relatively easy, but for one problem: I needed to delete objects outside the tree, for instance, from the item detail page and from the toolbar. I didn't want to embed the deletion request logic in the tree component's renderer. I needed the logic to be accessible anywhere; from links, buttons and images, anything that could handle an onClick.

Describing my solution to a colleague the next day took quite a while. Once I had successfully conveyed the design, my coworker paused for a moment and said, "Oh, you mean it's a Command?"

The Command pattern is a convenient way to express the intention of the AjaxCommand strategy, though there are some differences. I'll focus on the AjaxCommand strategy in this article, but use the Command pattern as a point of reference.

Note that I assume you are familiar with JSF and Ajax development in general. I've used the Dojo library for my example, but the idea applies regardless of how you accomplish your Ajax calls.

A Command pattern for Ajax?

In the canonical example, the Gang of Four Command pattern is used by a toolkit developer to enable future users to specify actions within the toolkit. Not knowing how the toolkit will be used, the developer codes to an interface, leaving the implementation details abstract.

The average enterprise Web application involves a similar scenario, where one person or group provides the tools to another. Although the typical Web application probably doesn't need to provide such complete autonomy between teams, other factors at work in a JSF-Ajax call suggest the use of the Command pattern; namely the layered architecture, the intermediary of HTTP, and JSF's component model.

Now, let's say you're working on a Web application where you need to do the following:

  1. Handle commands arriving as Ajax requests without isolating the logic in a component.
  2. Make it easy to add new commands.
  3. Delegate the business logic to the business layer.

We'll start by taking a look at how you would set up the server side of the request, based on the AjaxCommand strategy. After that we'll look at the front-end design, and I'll conclude by comparing the original Command pattern with the AjaxCommand variation.

AjaxCommand on the server-side

The AjaxCommand strategy uses a phase listener, which seems to be the most common approach to handling Ajax requests in JSF. AjaxCommand is the key interface.

Figure 1 shows the classes involved.

A class diagram of the AjaxCommand strategy

Figure 1. A class diagram of the AjaxCommand strategy

All of this is pretty straightforward. What you can't see in the diagram is that the PhaseListener gains its reference to the AjaxCommand instance by pulling a class name out of the request. Setting up the PhaseListener is easy, I'll walk through it in just a moment. In the meantime, Listing 1 shows the phase-listener method that does the work:

Listing 1. A hard-working PhaseListener method
public void beforePhase (PhaseEvent event){
...
if (AjaxRequestUtil.isAjaxCommand(context)){ // 1)
  if (log.isTraceEnabled()) { log.trace("Got ajax command..."); }
  String ajaxCommandBean = AjaxRequestUtil.getAjaxCommandBeanName(context); // 2)
  if (ajaxCommandBean == null){
    throw new NullPointerException(
      "This was an ajaxCommand request but no ajaxCommandBean name was found under key: " + AjaxCommand.AJAX_COMMAND_BEAN_KEY);
  }
  ValueBinding vb = context.getApplication().createValueBinding(
    "#{"+ajaxCommandBean+"}"); // 3)
  AjaxCommand command = (AjaxCommand)vb.getValue(context);
  command.handleAjaxRequest(context); // 4)
  // Call response complete, we are going to handle the response ourselves
  context.responseComplete();
}

The PhaseListener does the following in Listing 1 (note the numbered references in the above code sample):

  1. Checks to see if the event is an AjaxCommand request.
  2. Gets the AjaxCommand bean name from the request (throwing an exception if not found).
  3. Gets the AjaxCommand bean from a value binding.
  4. Executes the handleAjaxRequest method (you might check for null first).

I'll break each of these steps out into some detail in the next sections.

Step 1. Check to see if the event is an AjaxCommand request

The AjaxRequestUtil.isAjaxCommand() is called by the phase listener in Listing 2.

Listing 2. AjaxRequestUtil.isAjaxCommand()
public class AjaxRequestUtil {
...
public static boolean isAjaxCommand(FacesContext context){
  HttpServletRequest request =
    (HttpServletRequest)context.getExternalContext().getRequest();
  Map requestMap = request.getParameterMap();

  boolean ajaxKeyIsTrue = false;

  if (requestMap.containsKey(AjaxCommand.AJAX_COMMAND_KEY)){
    ajaxKeyIsTrue = "true".equals(request
      .getParameter(AjaxCommand.AJAX_COMMAND_KEY));
  }

  return ajaxKeyIsTrue;
}

In Listing 2 you check the request for a parameter under a given key (a constant AjaxCommand.AJAX_COMMAND_KEY that should be something unique, like: public static final String AjaxCommand.AJAX_COMMAND_KEY = "com.companyname.jsf.AJAX_COMMAND";). If that key is present and has the value you want, you return true.

Step 2. Get the AjaxCommand bean name from the request

Next, you use AjaxRequestUtil.getAjaxCommandBeanName() to pull the AjaxCommand bean name out of the request. Note that AjaxRequestUtil also uses a straightforward parameter in the request, as shown in Listing 3.

Listing 3. AjaxRequestUtil.getAjaxCommandBeanName()
public class AjaxRequestUtil {
...
public static String getAjaxCommandBeanName(FacesContext context){
  HttpServletRequest request =
    (HttpServletRequest)context.getExternalContext().getRequest();
  Map requestMap = request.getParameterMap();

  String ajaxCommandId = request.getParameter(AjaxCommand.AJAX_COMMAND_BEAN_KEY);

  if (ajaxCommandId == null){
    if (log.isWarnEnabled()) {
      log.warn("There was NO AjaxCommand bean name found under key: " +
        AjaxCommand.AJAX_COMMAND_BEAN_KEY); }
    }

    return ajaxCommandId;
  }
}

Step 3. Get the AjaxCommand bean from a value binding

In Step 3 you leverage JSF's managed bean facility. With the bean name in hand you do a programmatic lookup, which basically says, "Give me the bean bound under this name."

Here's the declaration of the bean in faces-config.jsf:

Listing 4. faces-config, DeleteItemCommand bean definition

...
  Command to delete item from Ajax Request
    DeleteItemCommand
    
      ajaxcommand.jsf.ajax.DeleteWithId
    
    request
    businessObjectUtil
      #{idUtil}
    
    deleter
      #{DeleteVoAction}
    
  
...




In Listing 4 you find the class under the logical name DeleteItemCommand. The class DeleteWithId implements the AjaxCommand interface. The "WithId" part of the name signifies that what you are deleting will be designated by an "Id", regardless of where the delete is called from. Note that if you needed to use more information than just an ID to identify the objects, it would be trivial to do that.

Note that you also parameterize DeleteWithId with a couple classes. The first, businessObjectUtil, is a utility class that allows you to look up the object based on Id.

A note about AjaxAction

"deleter" (DeleteVoAction) is an implementation of AjaxAction, as you can see if you refer back to Figure 1. The idea is to separate the actual knowledge of how to delete an object from the user interface layer and also allow the delete logic to be used elsewhere. That is, the AjaxCommand interface subclass, whose job it is to deal with the nuts and bolts of the request, employs the AjaxAction to decouple itself from the business logic.

In this example, the DeleteVoAction just delegates to a facade call. You might start out with the facade call in the AjaxCommand itself and refactor it out later.

You make some gains by moving the facade call into the AjaxAction. First, implementing an interface means the object that knows about the facade (which is presumably in the core of the application) can be placed into that package or project. That is to say, you may not want to include a reference from an AjaxAction subclass (which might be in a JSF/front-end specific project) to the facade class.

Another benefit of moving the logic out into an interface implementation is that you can defer the actual subclass to someone else, who may not even be a member of your team. This flexibility is useful if you are creating a framework for someone else. Such flexibility is the core motivation behind the GOF Command pattern, although it isn't such a common requirement for Web application developers.

Step 4. Execute the handleAjaxRequest method

Looking at the handleAjaxRequest method from the DeleteWithId will give you a more concrete idea of how the backing bean deals with the Ajax request.

Listing 5. DeleteWithId.handleAjaxRequest()
public class DeleteWithId implements AjaxCommand {
...
public void handleAjaxRequest(FacesContext context) {
  HttpServletRequest request =
    (HttpServletRequest)context.getExternalContext().getRequest();

  String id = request.getParameter("com.company.jsf.id"); // Get id

  BusinessObject bo = businessObjectUtil.getNodeFromId(id);

  if (bo == null){ log.info("Didn't find object with id: " + id); return; }
  boolean success = true;
  String detail = vo.getLiteralName() + " deleted.";

  try {
    deleter.action(bo);
  } catch (Exception e){
    if (log.isWarnEnabled()) { log.warn("Failed to delete entity: ", e); }
    success = false;
    detail = e.getMessage(); // Get error message for response
  }

    HttpServletResponse response =
      (HttpServletResponse)context.getExternalContext().getResponse();

    response.setContentType("text/xml");
    response.setHeader("Cache-Control", "no-cache");

    try {
      if (success){
        String responseString = new String(""+
          "OK"+
          ""+detail+""+
          ""+id+""+
          "");

          response.getWriter().write(responseString);
      } else {
        String responseString = new String(""+
    "ERROR"+
    ""+detail+""+
    ""+detail+""+
    ""+id+""+
    "");
    response.getWriter().write(responseString);
      }
    } catch (IOException ioe){
      if (log.isErrorEnabled()) { log.error(
        "Exception writing ajax response: ", ioe); }
    }
  }
...
}




The essence of this method is as follows:

  1. Get the ID for the object to delete from the request in string form.
  2. Get the Object from the ID (using the util class you parameterized in the managed bean declaration).
  3. Call the action method of deleter (which was also parameterized), passing it the object ID.
  4. Send a response to the user interface. Remember that JSF is now done with this request because you called responseComplete() back on the PhaseListener.

A quick look at deleter.action() (DeleteVoAction.action()) shows that this method just delegates the job:

Listing 6. DeleteVoAction.action()
public class DeleteVoAction implements JsfAction{
...
public void action(Object o){
  CoreVO[] nodePath = (CoreVO[])o;

  VOHelper.getFacade().deleteVO(nodePath);
}
...

Setting up the PhaseListener

The last step for the server side is to set up the phase listener, which is easy. Your first step is to define the listener in your faces-config.xml file, as shown in Listing 7.

Listing 7. faces-config, phase-listener

...

    ajaxcommand.toolbox.jsf.listeners.AjaxListener
  

...




Next, in the listener itself, you tell JSF when to execute the listener. Start by telling it what phase to run in, like so:

public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

You then have a choice of running in the public void afterPhase (PhaseEvent event) method or the public void beforePhase (PhaseEvent event).

In this case you'll choose to run the listener in the RESTORE_VIEW phase. You are handling an Ajax request, so you want to avoid the rest of the processing life cycle. In this example, you implement the event handling in the beforePhase method. Because you aren't relying on any view components, you don't even require the view to be present. The managed bean does the work for you.

AjaxCommand on the front end

That pretty much takes care of the server side, where you catch the Ajax request, process it and fire off a simple response. Now let's take a look at the front end, where you set up the initial Ajax request and then catch the server's response.

Setting up the Ajax request

One of the main motivations for using the AjaxCommand strategy is that you can use a given AjaxCommand from anywhere you can make an Ajax request. So, as long as you can send an Ajax request with how to identify the node, the AjaxCommand flag set to true, and the bean name of the managed bean that implements AjaxCommand, you can use your action. Basically, anywhere you can put JavaScript, you can put a call to an AjaxCommand, and the server-side handler code will handle it -- which is pretty neat.

Now let's look at the steps to set up the initial Ajax request, starting with a call invoking the DeleteCommand.

Listing 8. Ajax Delete call
tools.doDelete = function(domNode, id){
  var confirmation = confirm("Delete this item?");

  if (!(confirmation)){ return; }

  var urlToPost = dojo.dom.getFirstAncestorByTag(domNode, "form").action;

  params = {
    "com.asparity.jsf.AJAX_COMMAND" : "true",
    "ajaxcommand.jsf.AJAX_COMMAND_BEAN" : "DeleteItemCommand",
    "id" :  id
  }

  dojo.io.bind({
    url: urlToPost,
    method: "POST",
    load: function(type, data, evt){ tools.handleDelete(data); },
      mimetype: "text/xml",
      content: params
  });
}

Leveraging Dojo's built-in Ajax ability makes this quick and painless. First, verify that the user really wants to delete. Next, grab the URL you want to submit to from the enclosing form -- this will be your server. To get the form, your function requires a domNode be passed in, but that's OK because it can be any old domNode.

After that, you create an associative array to hold the parameters, which (as noted above) are the AjaxCommand flag, the command bean name, and the means for identifying the node you want to delete.

dojo.io.bind is really a winner --- you can just see how simple it is. Simply tell it what URL to use, and what method, then the rest (although the syntax is compact) just says, tools.handleDelete is the callback function, the mime-type, and hands the parameters over.

Your next step is to set up the Ajax response handler, which will catch the server's response.

Setting up the Ajax response handler

You make the server's response available to your callback handler by passing the "data" callback variable like so:

tools.handleDelete(data)

The callback handler is executed when the Ajax response is sent, and the server's response is passed in as an argument. Here's the handler:

Listing 9. Ajax response handler
treeTools.handleDelete = function(statusXml){
  var status = statusXml.getElementsByTagName("status")[0].childNodes[0].nodeValue;
  var detail = statusXml.getElementsByTagName("detail")[0].childNodes[0].nodeValue;

  var id =
    statusXml.getElementsByTagName("id")[0].childNodes[0].nodeValue;

  if (status=="OK"){

    // ... Remove the object from the UI based on ID

    alert(detail);

  } else { // Error
    //  "Problem deleting item: " + detail);
    alert("Problem deleting: " + detail);
  }
}

The handler grabs values from the XML response and responds appropriately. If everything went okay, you pop up an alert saying so; if not, you give an alert with the server's description of what went awry.

And with that, the request cycle is complete. Anywhere you have a way of identifying an object, you can delete it via an Ajax call.

An obvious improvement that you almost always need in a production system is a way to identify what element on the page made the call, and how it should respond. For instance, if this delete call is made from a detail screen, you would want to clear that screen. If it was made from a list or a tree of items, you'd want to remove the item or the node.

A good way to handle the above requirement is, when you make the request, to add the domNode.id to the parameters. Then, when the server makes its response, add another element to the XML response, called something like ajaxcommand.jsf.AJAX_RESPONSE_ELEMENT_ID. The response handler can then grab that ID, and use getElementById to figure out who made the request and what the appropriate action is.

The Command pattern vs. the AjaxCommand strategy

The essential motivations or "forces" behind the GOF Command pattern and the AjaxCommand strategy are different. In the Command pattern, the main issue is that you don't know what commands will be added, nor how they function, yet you want to provide a mechanism for allowing commands to be added. While that could conceivably be a motivator for the AjaxCommand strategy, the main thing you are working around is sending out calls over HTTP, handling them in a modular way, and respecting the layers of the application design. The AjaxCommand design also provides a standard and easy way to add new commands to a Web application as it grows over time.

As is often the case, this example of a "real world" pattern is akin to, but not exactly like, what's laid out in the textbook. A fascinating thing is that even though the class diagrams are different, the spirit is definitely there. This makes the "pattern" designation useful. I consider the AjaxCommand strategy a variant, or modification, or just "similar to" the GOF Command pattern. Doing so enables me to sketch out, in a general way, what I am trying to accomplish. As a point of reference, take a look at the original Command pattern class diagram in Figure 2.

A class diagram of the GOF Command pattern

Figure 2. A class diagram of the GOF Command pattern

Figure 2 is the generic class diagram found in the GOF book. You'll notice that the AjaxCommand class diagram in Figure 1 doesn't look a whole lot like it (and neither do the GOF examples of the pattern, for that matter).

Still, it isn't hard to see the mapping between the two patterns. In the AjaxCommand implementation, the Command client is the JSF application, which sets the receiver on the command using the managed bean facility. The receiver, which knows how to accomplish the action, is the JsfAction subclass, DeleteAction. The ConcreteCommand role is played by the DeleteCommand, and AjaxCommand takes over for the Command interface. Finally, the Invoker is the PhaseListener, which tells the command to fire.

In conclusion

Ajax is a fun technology, all cautions about its potential for overuse acknowledged. In the right situations it can really avoid some unnecessary processing and network traffic, and make for a nice user experience. JSF is a cause for ambivalence among many developers, but I believe that it will supersede the other front-end frameworks, because it capitalizes on component reusability. Over time, the JSF community's work on components that are readily pluggable will leave other frameworks behind. Also, the difficulties associated with JSF are being addressed and solutions will gradually become well understood and more readily available.

Getting the two technologies to work together is a common requirement, and will become more so. In this article I've shown you a nice trick for certain occasions, when you need to access Ajax functionality without embedding it in a JSF component.

I'd like to hear about your experiences using the JSF AjaxCommand strategy. Join the discussion below to let me know what you think!

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/71047/viewspace-996788/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/71047/viewspace-996788/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值