XMPP Forms

Up till now, all my examples pertaining to exchanging data between the client and the external component have been using <message>. However there is a more structured way of exchanging data using forms (XEP-0004) very much like HTTP forms. The XMPP forms has a very rich structure; the following points provide a quick overview of XMPP data forms
  • firstly all the fields in the form are typed (or not, if you so choose); types includes ‘primitives’  like String, JID, boolean. Field can have attributes like hidden, fixed (label) and password. Furthermore fields can also be multivalued viz. a collection. The following shows an example of a XMPP form

<x xmlns=’jabber:x:data’ type=’form’>
   <title>Bot Configuration</title>
   <instructions>Fill out this form to configure your new bot!</instructions>
   <field type=’hidden’ var=’FORM_TYPE’>
      <value>jabber:bot</value>
   </field>
   <field type=’list-multi’
         label=’What features will the bot support?’ var=’features’>
      <option label=’Contests’><value>contests</value></option>
      <option label=’News’><value>news</value></option>
      <option label=’Polls’><value>polls</value></option>
      <option label=’Reminders’><value>reminders</value></option>
      <option label=’Search’><value>search</value></option>
      <value>news</value>
      <value>search</value>
         …
</x>

  • each form is denoted by a “type” which defines the semantics of the form. The type can be form, submit, cancel and result. In the example above, we see that the type is ‘form’; this means that the party that is submitting the form is asking the receiver to fill in the form.
  • XMPP forms can also contain multiple items; for example when you submit a Google search, Google will list multiple results in a page. All these result items contain the same structure like a text heading, a URL, a short description of the result. XMPP form can model this structure. A form of this type consist of 2 parts: a ‘header’, between a pair of <reported> tags, contains the structure of each <item> and the item itself with just the values. The following is an example of this type of form

<x xmlns=’jabber:x:data’ type=’result’>
   <title>Google Search: verona</title>
      <reported>
         <field var=’name’/>
         <field var=’url’/>
      </reported>
      <item>
         <field var=’name’>
            <value>Comune di Verona – Benvenuti nel sito ufficiale</value>
         </field>
         <field var=’url’>
            <value>http://www.comune.verona.it/</value&gt;
         </field>
      </item>
      <item>
         <field var=’name’>
            <value>benvenuto!</value>
         </field>
         <field var=’url’>
            <value>http://www.hellasverona.it/</value&gt;
         </field>
      </item>
         …
</x>

  • A XMPP form can be embedded into any of the 3 XMPP packets; the implication of this is that a XMPP form can be used in many different context for example a form can be used in an IQ packet to configure a MUC room or in a <message> packet to solicit details from a user

@DataForm Annotation

Vorpal allows you to manipulate data forms very easily through @DataForm annotation together with some powerful mapping capabilities. There are 2 use cases: the first is unmarshalling/unserializing a data form from a packet and the second is creating a data form to be returned as result.

Processing DataForm

Lets look at capturing a data form from an incoming packet. Assume we have the following incoming packet

<iq id=”F80xW-15″ to=”query@customer.batcomputer” type=”get“>
   <query xmlns=”uri:customer_query“>
      <x xmlns=”jabber:x:data” type=”form”>
         <field var=”customerId” type=”text-single”>
            <value>1</value>
         </field>
         <field var=”name” type=”text-single”/>
         <field var=”addressline1″ type=”text-single”/>
         <field var=”addressline2″ type=”text-single”/>
         <field var=”city” type=”text-single”/>
         <field var=”zip” type=”text-single”/>
         <field var=”state” type=”text-single”/>
         <field var=”phone” type=”text-single”/>
         <field var=”fax” type=”text-single”/>
         <field var=”email” type=”text-single”/>
         <field var=”discountCode” type=”text-single”/>
         <field var=”creditLimit” type=”text-single”/>
      </x>
   </query>
</iq>

In the packet example shown above, the packet contains a data form in <query>. The idea here is that we have an empty form except the customerId field. query@customer.batcomputer service will lookup the customer id (customerId field), from the database, fill in the form and return it to the submitter (data form type is form). The following code shows how we might handle this packet

@RequestScoped
@IQ
@Query(“uri:customer_query”)
public class ProcessQuery {
   @PersistenceContext EntityManager em;

   @Field(name=”customerId”, parameter=”{custId}”)
   public Object process(@Bind(“custId”) int custId) {
      Customer customer = em.find(Customer.class, custId);
      //etc. etc
   }

Lets go through the code

  1. we have a @Query (see disco blog) annotation which handles only uri:customer_query namespaced query.
  2. Next we have a @Field annotation which only fires process() method if the data form has a field call customerId. By using a @Field annotation we are assuming that the packet has a data form. Vorpal will look for a XMPP data form in the ‘usual’ places; in <iq> that ‘usual’ place translate to inside the child of <iq> packet which in this example will be in <query>. For <message> and <presence>, Vorpal will look for a data form as a direct child of <message> and <presence> packets. Any other location you’ll have to use XmlPath annotation, which is an annotation for XPath, from com.kenai.jabberwocky.framework package

Instead of using @Field to fire process(), you can also use @DataForm annotation:

   @DataForm
   public Object process() {

process() will fire if there is a data form in the packet in the ‘usual’ place. Of course that example complete ignores how we get the customer’s id which will be the subject of our next section.

Unmarshalling DataForm

There are 3 ways that you can get a data form into your message handler; the first and simplest way is to simply inject it into a DataForm object. The following is an example of how you do this

@RequestScoped
@IQ @Query(“uri:customer_query”)
public class ProcessQuery {
   @PersistenceContext EntityManager em;  
   @DataForm org.xmpp.forms.DataForm customerForm;

A second method is to inject the form into a Map (map injection). As you might have guessed, the keys of the Map are the form’s name, and the value(s) are converted into their default Java type based on the field type. The following table shows this conversion.

DataForm type Default Java type
boolean java.lang.Boolean
fixed fixed fields are ignored. Will also not appear in map injection and form binding (see below)
hidden See text-single below
jid-single org.xmpp.packet.JID
text-single java.lang.Boolean if it is ‘true’/’false’, java.util.Date if we can parse it, java.lang.Long or java.lang.Double if it is a ‘numeric’ string, java.lang.String otherwise
text-private char[] viz. an array of character. Inspired from JPasswordField
jid-multi java.util.Collection<JID>
list-multi java.util.Collection<Object> where Object will follow the conversion rule of text-single
text-multi java.util.List<String>
list-single As text-single

If the field type is missing, Vorpal will try to guess. So you can inject a data form into a Map like so

@DataForm Map<String, Object> customerForm;

or

//Using the non generic Map which is exactly the same as above
@DataForm Map customerForm;

You can also use Map<String, String>; in this case all the values will all be String. For ‘multi’ field types like text-multi, list-multi, jid-multi, the value will be a comma separated list of the field values. Besides injecting the form values into a Map, you can also get the form field’s type by specifying FormField.Type as the second parameterized type in Map. The following example shows how forms injection works

//Map the value
@DataForm Map<String, Object> customerForm;
//Map the field type
@DataForm Map<String, FormField.Type> fieldType;

for (String fieldName: fieldType.keySet()
   System.out.println(“Field name: %1$s, value: %2$s, type: %3$s”
         , fieldName, customerForm.get(fieldName), fieldType.get(fieldName));

A third way to read the value from a data form is to use forms binding. Forms binding is very similar to JAXB in that you map a data form into a Java object. The following Java object creates a binding to our customer query form example shown above

@DataForm
public class Customer {
   …
   public int getCustomerId() { …
   public void setCustomerId(int id) { …

   @FormField(name=”addressLine1″)
   public String getAddress1() { …
   public void setAddress1(String addr1) { …

   …
}

  • Firstly annotate your Java object with @DataForm to tell Vorpal that you want to use this class to bind (marshall/unmarshall) to a form. Vorpal will then try to match your JavaBean property names with the field name and copy the values from the form to the instance or vice versa. Type conversion is based on the type of the form
  • If for some reason, you need to override the above behaviour, annotate the JavaBean accessor with @FormField providing the field name that we should bind to and optionally the type
  • At the moment forms binding does not have an equivalent @XmlTransient to ignore a property

Now to bind Customer to a data form, do the following

@DataForm Customer customerForm;

Marshalling DataForm

After you have process the customer query, you’ll now need to return the form to the sender. Again the simplest way to do this is to create a DataForm object. If you do not wish to manipulate DataForm directly, Vorpal has a utility class call DataFormBuilder. Assume you would like to create the following data form

<x xmlns=”jabber:x:data” type=”form”>
   <field var=”customerId” type=”text-single”>
      <value>1</value>
   </field>
   <field var=”name” type=”text-single”>
      <value>JumboCom</value>
   </field>
   <field var=”address” type=”text-multi”>
      <value>111 E. Las Olas Blvd</value>
      <value>Fort Lauderdale</value>
      <value>33015 FL</value>
   </field>
   …
</x>

you perform do the following

DataFormBuilder builder = DataFormBuilder.create(DataForm.Type.result);
builder.field(“customerId”).textField(“1”)
      //The following is a longer version of .textField()
      .field(“name”).type(FormField.Type.text_single).value(“JumboCom”)
      .field(“address”).textField(“111 E. Las Olas Blvd”, “Fort Lauderdale”, “33015 FL”);
DataForm result = builder.build();

DataFormBuilder uses the fluent style API so using it is quite straightforward. If your form contains multiple result, DataFormBuilder handles that as well

DataFormBuilder builder = DataFormBuilder.create(DataForm.Type.result);
//Build the ‘header
builder.startReported()
      .field(“customerId”).type(FormField.Type.text_single)
      .field(“name”).type(FormField.Type.text_single)
      .field(“address”).type(FormField.Type.text_multi)
      //Other headers…
.endReported();

//For each row
builder.startItem()
      .field(“customerId”).value(“1”)
      .field(“name”).value(“JumboCom”)
      .field(“address”).values(“111 E. Las Olas Blvd”, “Fort Lauderdale”, “33015 FL”)
      …
.endItem();

//Next row
builder.startItem()
      .field(“customerId”).value(

You can also return a Map as your result. This is the reverse of the marshalling process that I’ve described above. If you return multiple result, then return a list (or any Collection) of Maps like so List<Map<String, Object>> or just List<Map>.

Finally results can also be returned using forms binding. Again if you returning multiple result using forms binding, then return a list of your objects eg. List<Customer>

Lets complete our ProcessQuery handler above using forms binding to return the result

@RequestScoped
@IQ @Query(“uri:customer_query”)
public class ProcessQuery {
   @PersistenceContext EntityManager
   @Field(name=”customerId”, parameter=”{custId}”)
   public Object process(@Bind(“custId”) int custId) {
      Customer cust = em.find(Customer.class, custId);
      if (null == cust)
         return (ErrorPacketBuilder.create(Condition.item_not_found));
      //Vorpal will default form to result if type is not specified
      return (cust);
   }
}

Since Customer supports both JPA and forms binding we must make sure that it has the appropriate annotations

@Entity // for JPA
@DataForm
public class Customer {
   …

Download the latest bundle here (look for “Latest XMPP Project” entry) if you intend to try out Vorpal.

Till next time

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: