Vorpal Framework – A Second Try

After releasing the first version of Vorpal framework, I re evaluated at the programming model and felt that it was a little too limited. As I’m not a very experienced API designer, I relooked at JAX-RS (which Vorpal is based on) and CDI (which I hope to integrate with in the next release). I got some ideas from those API which I’ll outline in this blog.

Capturing Message Elements

Assuming we are going to process the following incoming message

<message
    from=’romeo@shakespeare.lit/orchard’
    to=’juliet@capulet.com’
    type=’chat’>
  <thread>act2scene2chat1</thread>
  <body>
    I take thee at thy word:
    Call me but love, and I’ll be new baptized;
    Henceforth I never will be Romeo.
  </body>
  <active xmlns=’http://jabber.org/protocol/chatstates’/&gt;
</message>

We write the following handler

@Message @To(“juliet@capulet.com”)
public class ToJuliet {
   @From(“romeo@{anydomain}”) @Body(“{body}”)
   public String processToJuliet(@Bind(“body”) String body) {
      return (“*Sigh* Oh Romeo – ” + body.toUpperCase());
   }
   @From(“{everyone}@{anydomain}”)
   public String processEveryoneElse(@Bind(“everyone”) String others) {
      return (other + “, you are not my Romeo”);
   }
}

First off, the @Message indicates that this class is a message processor. The @To, @From and @Body annotations at the class and methods are pattern matcher. These matchers look for certain patterns in the incoming message (elements and attributes) and if the message attributes and elements match these patterns, then that particular handler (class) will be selected.

In this example, we have a class level matcher; for ToJuliet handler to be ‘fired’, the incoming message must be addressed to juliet@capulet.com. Happily for us, it is. The next step is to find which method we should use. Again we examine the annotations from more specific (more annotations) methods to more general. In this case we examine processToJuliet() first followed by processEveryoneElse() (see Method Resolution in this blog).

You can specify how an annotation should match the message by specifying a pattern string. In @From(“romeo@{anydomain}”), we say that the incoming from JID must match the literal string ‘romeo@’ and the remainder of the JID is to be captured by {anydomain}. For our sample message, the from JID matches ‘romeo@’ and ‘shakespheare.lit/orchard’ is captured by {anydomain}. So we select processToJuliet() for handling the message.

If you wish to use what is capture in the method then you use a @Bind (previously known as @Parameter) annotation to bind the capture to a parameter. Since we are going to use the body of the message, we specify @Body(“{body}”) to capture the contents of the body and then bind that to the formal parameter body with @Bind(“body”). For a detailed discussion on capture see Example 2 in this blog.

Anyone else besides Romeo sends a message to Juliet, then that message will be handled by processEveryoneElse().

Injecting Values

Up till this point, all values that we are using are from capture viz. we use @Bind to map an attribute to a parameter. This way of getting values from the incoming message forces you to match every attributes and elements.

Vorpal supports message attributes and elements injection. Suppose you would like the message body to be available to processEveryoneElse() without matching it, you can do the following

@From(“{everyone}@{anydomain}”)
public String processEveryoneElse(@Bind(“everyone”) String others, @Body String body) {
   …

If you want chatstates to be available to all the methods in ToJuliet, you can inject it into a member like so

@Message @To(“juliet@capulet.com”)
public class ToJuliet {
   @XmlElement(uri=”
http://jabber.org/protocol/chatstates“) private Element chatstates;
   …

In the above example, if there is a chatstate element in the incoming packet, the it’ll be injected into Element. Furthermore you can inject most values into several different types of object depending on how you use them. For XML elements , you can either inject it into an Element, or a String type, or a JAXB object. Since XMPP messages are XML documents, you can use XmlElement to inject any part of the message into a member or a parameter.

Some elements can be injected into specific type eg. the from and to JID can be injected into JID type, <thread> can be injected into ThreadID, etc. I’ll list all the available mappings in a future blog.

Finally you can also inject class level capture into field as well as formal parameters. Here is an example of injecting chatstates into a field

@Message @To(“juliet@capulet.com”)
@XmlElement(parameter=”{chatstates}”, uri=”
http://jabber.org/protocol/chatstates“)
public class ToJuliet {
   @Bind(“chatstates”) private Element chatstates;
   …

There are a few predefined capture that Vorpal automatically provides

  • __domain__ – the domain of the deployed component
  • __packet__ – the current Message
  • __componentManager__ – the ComponentManager
  • __conversation__, __threadID__, __conversationContext__ – used for conversation support which I will discuss in the next section

For example if you wish to have access the entire message, simply inject it into Message type

@Message @To(“juliet@capulet.com”)
public class ToJuliet {
   @XmlElement(uri=”
http://jabber.org/protocol/chatstates“) private Element chatstates;
   @Bind(PredefinedBindings.PACKET) Message message; //__packet__

One final note before we leave the value injection topic, it may be a little confusing with Vorpal annotation. After all you could use an annotation like @Body as a pattern matcher as well as an injection point. One good way to remember is that if an annotation is use to annotate a class or a method, then its a pattern matcher; and if the same annotation is use to annotate a field or a formal parameter then its an injection point.

Conversation Support

One of the feature in XMPP Message is to be able to support session or conversation thread. To participate in a conversation , all exchanged messages between the various parties must have the <thread> element; furthermore all <thread> must have the same thread id. An example of a threaded message is shown at the start of this blog. For more information on conversation see Best Practices for Message Threads.

Vorpal has built-in support for threads or conversation. So what is the benefit of conversation support? Simply put, stateful objects. In all our examples so far, the objects are request scoped. With every new request, Vorpal will create a new instance of a handler to process the message. This makes keeping track of state crumblesome.

With conversation support, the same instance of message handler will be used, if it the message matches the annotations. So you can keep states in the message handler. To use conversation, you have to inject Conversation type into your handler. Here is an example of how you do it

@Message @To(“juliet@capulet.com”)
public class ToJuliet {
   @Bind(PredefinedBindings.CONVERSATION) private Conversation conversation;
   private StringBuilder messageLog;
   @Body(“start”)
   public void startLog() {
      if (conversation.isTransient()) {
         conversation.begin();
         messageLog = new StringBuilder();
      }
   }
   @Body(“end”)
   public void endLog() {
      if (!conversation.isTransient()) {
         conversation.end();
         //Do something with messageLog
         …
      }
   }
   @From(“romeo@{anydomain}”) @Body(“{body}”)
   public String processToJuliet(@Bind(“body”) String body) {
      String result =
“*Sigh* Oh Romeo – ” + body.toUpperCase();
      if (null != messageLog)
        messageLog.append(result).append(“\n”);

      return (result);
   }

To start a logged session with Juilet, we have to first send juilet@capulet.com a message with the word start. This will trigger the handler to mark the conversation as started.

The conversation semantics is the same as CDI. See this tutorial if you are unfamiliar. Conversation have to be enabled explicitly by invoking Conversation.begin(). To end the conversation, call Conversation.end(). When you invoke end(),  Vorpal will add the gone chatstate

<gone xmlns=’http://jabber.org/protocol/chatstates’/&gt;

in the reply message back.

Conversation support is really about both communication parties honouring the <thread> element. In some situation, the incoming message do not have the <thread> element. You can still invoke Conversation.start() and Vorpal will insert a <thread> element, with a randomly generated id, in the reply message. However it is up to the client to decide if the next message will contain the <thread> element. If the client does not reply in kind, then there will be no conversation support.

You can find out if an incoming message have the required <thread> element by injecting ThreadID type.

@Message @To(“juliet@capulet.com”)
public class ToJuliet {
   @Bind(PredefinedBindings.CONVERSATION) private Conversation conversation;
   @Bind(PredefinedBindings.THREAD_ID) private ThreadID threadId;
   private StringBuilder messageLog;
   @Body(“start”)
   public void startLog() {
      if ((null != threadId) && conversation.isTransient()) {
         conversation.begin();
         messageLog = new StringBuilder();
      }
   }

Conversation support is not just limited to one handler. Let say you have more that one message handler class in your application; now if one or more of these message handlers are selected for ‘firing’ then all of these will be grouped under the same conversation. When you end the conversation, all these objects will also be destroyed. The ConversationContext type allows all message handler instances from the same conversation to share data among themselves. ConversationContext behaves very much like HttpSession. The ConversationContext is also injected into your message handler. One caveat is that ConversationContext is available only after the start of a conversation, viz at the point when you invoke Convesation.start() the injected ConversationContext member will be null. It is available in the next message handle cycle. I do currently have a solution (more injected objects!) but I’ve not implemented it.

The latest Jabberwocky bundle is available here. If you are trying it, let me know what you think. I’ve not tested it extensively yet so I’m sure there are a few bugs lurking. Let me know of them as well.

My next plan for Jabberwocky is to implement CDI support. I’ve got some code in but its still not working. With CDI, I hope to

  • provide a richer injection model
  • to allow message handlers to access JavaEE resources such as Stateless Session Bean, EnityManager, etc.

Till next time

%d bloggers like this: