Lifecycle, Handling Errors, Service Discovery Registration and Response Context

In this blog I’ll talk about 4 enhancements that I’ve recently added to Vorpal. These are not real game changer and if you do not use any of these, its no big deal. However having said that it does make using Vorpal a little easier.

Lifecycle

The first of these enhancement is lifecycle viz. you are able to listen to your component’s lifecycle and perform initialization or cleanup operations. There are 4 stages and they occur in the following order

  1. Pre start – before Vorpal makes a connection to the server
  2. Post start – occurs after a connection have been made to the server. This is also the first time you can send a packet out. We have not entered the application yet
  3. Main application loop – this is not really an event but the stage where your Vorpal application starts to receive packets
  4. Pre shutdown – occurs just before we are disconnected from the server. This is also the last time you can send out a packet. No received packets will be routed to your application
  5. Post shutdown – your Vorpal application is no longer connected to the server

You have to use CDI events to capture the lifecycle events. The following code shows how this is done

@RequestScoped
public class Lifecycle {
    private void preStart(@Observes ComponentPreStart preStartEvent) {       
    }
    private void postStart(@Observes ComponentPostStart postStartEvent) {
    }
    private void preShutdown(@Observes ComponentPreShutdown preShutdownEvent) {
    }
    private void postShutdown(@Observes ComponentPostShutdown postShutdownEvent) {
    }
}

Note the Java class to observe for a particular lifecycle stage. The event objects allow you to

  • statically register identities, feature and items. We will cover this in later in this blog
  • get a reference to the component, component manager, the JID of your component, the subdomain, etc.
  • get and set properties
  • send packets

Error Handling

Instead of manually creating an error packet, Vorpal lets you indicate error by returning either PacketError.Conditon and/or PacketError.Type. Below is an example from handleQuery() from CustomerQueryHandler.

@Body("{body}") 
public List<div class="youtube-video"><object> handleQuery(@Named("body") int custId) {   
   List<div class="youtube-video"><object> result = new LinkedList<div class="youtube-video"><object>();
   Customer customer = em.find(Customer.class, custId);
   if (null == customer) {
      result.add("Customer not found: " + custId);
      //Adds error condition to the return packet      
      result.add(PacketError.Condition.not_acceptable);   
   } else {      ...   }   
   return (result); 
}

Since we cannot find the custId, we return a message packet with the appropriate error condition. The above would result in the following packet<message to="fred@batcomputer/pidgin" from="query@customer.batcomputer"

<message to="fred@batcomputer/pidgin" from="query@customer.batcomputer<br />      type="error"><br />   <body>Customer not found: 456</body><br />   <error code="406" type="modify"><br />      <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/><br />   </error><br /></message>

Of course you can also just return a PacketError object.

Service Discovery Registration

You can now statically register identity, feature and item if these need not be dynamically generated. By default, all Vorpal component will return the following in response to a disco#info

<identity category='component' type='generic' name='the name of your component'/><br /><feature var='http://jabber.org/protocol/disco#info'/><br /><feature var='urn:xmpp:ping'/><br /><feature var='jabber:iq:last'/><br /><feature var='urn:xmpp:time'/>

There are no items return by disco#items. You can completely remove, add or change these default identity and feature via static registration. To modify these, you need to get a reference to ComponentContext. You can get it by the following 2 ways

  • From the lifecycle event ComponentPreStart, ComponentPostStart, ComponentPreShutdown and ComponentPostShutdown
  • By injection. There are 2 ways of doing this
    • @Inject ComponentContext
    • @Bind(“__componentContext__”) ComponentContext. You can also use @Named like so @Inject @Named(“__componentContext__”) ComponentContext

Lets look at an example of how you can statically register identity, feature and items

private void preStart(@Observes ComponentPreStart preStartEvent) {
   ComponentContext context = preStartEvent.getComponentContext();
   context.identity(new IdentitySpecification(“component”, “bot”, “Customer Query”));
   context.add(new FeatureSpecification(“uri:customer_query”);
   context.add(new ItemSpecification(“info@{__subdomain__}“);
}

In the above code example, you’ll notice that we are adding (appending) a feature and an item to our component. You can use ‘capture’ when you are specifying items; as you can see, when we are adding info entity we do not know what is the final subdomain name is, so we use the capture {__subdomain__} or the constant PredefinedBindings.PARAMETER_SUBDOMAIN. Vorpal will perform the substitution for you.

If you want to completely remove the default identity and features then use ComponentContext.identity() and ComponentContext.feature() respectively to set new identitys and features. If we return to the example above, you will notice that we use identity() which means that we are deleting the existing identity list (component/generic) and replacing that with component/bot.

The static registration works in combination with dynamically generated disco#info and disco#item to produce the final service discovery packet. Dynamically generated here means that you have defined a disco#info or disco#items handler (see this blog and this screencast). If you want only want to dynamically generate your disco#info and disco#items then define a handler service discovery and delete the default identities and features like so

context.identity();
context.feature();

Response Context

Before going into response context, let me explain a few concepts regarding responses. Look at the following code snippet

@Body(“{body}”)
public String handle(@Named(“body”) String body) {
   if ((null != body) && (body.trim().length() > 0))
      return (body.toUppercase());
   return (“Whats up?”);
}

Its a very simple message handler; when we return the result String, Vorpal will create the reply back according to the original packet. You can in fact return a number Java objects like JAXB objects, DataForm, Presence.Show.chat, Map, etc. Depending on the received message, Vorpal is able to construct a correct packet to send back to the recipient; eg a received Message will result in Message, a IQ get will result in IQ set, etc. The response context allows Vorpal to deal with what you return correctly.

You can also return a Packet; if you do that, then Vorpal will not perform any processing on the Packet but will just send that out. One of the disadvantages of using a Packet is that there are lots more things for you to do for example, filling in the recipient, creating the query child, etc. Furthermore, if you are sending multiple message then you have to creating multiple Packet; the default way of returning Java object does not work as Vorpal it assumes you only want to send 1 packet back to the recipient.

The response context API is a simple Java class that gives you the user friendliness of leveraging on the response context and the flexibility of constructing of a Packet yourself. It purpose is firstly to solve working with Packets directly and secondly to allow you to work with any of the following packet exchange patterns:

  • one packet to one recipient – one reply back to the same sender
  • many packet to one recipient – multiple reply (different packets) back to the same sender
  • one packet to many recipient – one reply back to multiple recipients, including the sender
  • many packet to many recipient – multiple reply back to multiple recipients, including the sender

Lets look at how we go about doing this

One packet to one recipient

This is the default case. Any thing that you return will be send back to the recipient; the recipient here meaning the sender of the packet that has caused your message handler to fire. In cases where a packet is not expected like a Presence handler, you have to use a Packet to override the response context. You can now return a ResponseContext object instead. 

@PresenceType(Presence.Type.chat)
public Object sayHello(@From JID from) {

   ResponseContext responseContext = new ResponseContext();
   responseContext.type(ResponseContext.Type.Message)
         .add(“Hello ” + from);

   return (responseContext);
}

If we did not use ResponseContext, then Vorpal will interpret whatever you return based on the response context which is a presence packet. We did not need to add the recipient’s name to responseContext because whatever information that is missing, Vorpal will fill that in from the originating message.

Many packet to one recipient

In some situation you would like to send multiple messages back to the recipient. Look at the following message handler from CustomerServiceDiscovery example which you can find in the playground

@To(“query@” + PredefinedBindings.PARAMETER_SUBDOMAIN)
@Query(PredefinedBindings.DISCO_INFO)
public List
queryDiscoInfo() {
   List<Object> result = new LinkedList<Object>();
   result.add(new IdentitySpecification(“client”, “bot”));
   result.add(Constants.CUSTOMER_QUERY);

   //Create a second packet to send a instruction as message
   //Remember to turn off bot sentry if you are using Pidgin
   ResponseContext responseContext = new ResponseContext();
   responseContext.type(ResponseContext.Type.Message)
         .add(“Customer query components allows you to query database by entering the customer id”);
   result.add(responseContext);

   return (result);
}

Notice that we first construct our reply (order is not important) to a disco#info; the reply is shown in green. We then construct a second packet using ResponseContext API. This is shown in blue. We say that the second message is going to be a XMPP message, then we add a String to the responseContext object. This is exactly how we would have done had this been in response to a XMPP message. We then add the responseContext to or reply.

Vorpal will now process this and send off 2 packets, one IQ reply and the other a Message.

The result is that whenever you perform a disco#info on query@some_subdomain, queryDiscoInfo() will send back 2 packets; the first is a response to the disco#info and the second is an XMPP message telling the entity who send the disco#info how to perform a query.

The following is a equivalent to the above method

@To(“query@” + PredefinedBindings.PARAMETER_SUBDOMAIN)
@Query(PredefinedBindings.DISCO_INFO)
public List queryDiscoInfo() {
   List<ResponseContext> result = new LinkedList<ResponseContext>();
   ResponseContext responseContext = new ResponseContext();
   responseContext.add(new IdentitySpecification(“client”, “bot”));
   responseContext.add(Constants.CUSTOMER_QUERY);
   result.add(responseContext);
   //Create a second packet to send a instruction as message
   //Remember to turn off bot sentry if you are using Pidgin
   responseContext = new ResponseContext();
   responseContext.type(ResponseContext.Type.Message)
         .add(“Customer query components allows you to query database by entering the customer id”);
   result.add(responseContext);
   return (result);
}

In the first responseContext instance we did not even tell it the message type; since message type is missing, Vorpal will set that to be a IQ reply message.

You cannot add a ResponseContext instance to another ResponseContext instance in other words you cannot embed ResponseContext within a ResponseContext.

One packet to many recipient

If you wish to send the same packet to multiple recipient with ResponseContext, you need to fill in the to() method. Once you have filled in the to(), Vorpal will not insert the recipient, so do keep that in mind. The following code snippet sends out multiple Presence message

ResponseContext presence = new ResponseContext();
presence.type(ResponseContext.Type.Presence)
      .to(“fred@bedrock”, barney@bedrock”, “holmer@springfield”)
      .add(Presence.Show.chat)
      .add(“I’m available”);

Many packet to many recipient

Finally in many to many pattern, just return a Collection of ResponseContext and each ResponseContext have multiple recipient. The ResponseContext can be of different message type.

Let me know what you think of these enhancements to Vorpal. You can find the latest bundle here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: