JabChapter 10

From WikiContent

Revision as of 01:33, 26 July 2010 by Newacct (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Programming Jabber
Preface
1 - Getting started with Jabber
2 - Inside Jabber
3 - Installing the Jabber server
4 - Server Architecture and Configuration
5 - Jabber Technology Basics
6 - Jabber Namespaces
7 - User Registration and Authorization
8 - Using Messages and Presence
9 - Groupchat, : Components, and Event Models
10 - Pointers for Further Development
Appendix A
Appendix B
Colophon

Contents

Pointers for Further Development


The previous two chapters have demonstrated how Jabber can be used to build applications and solutions in many functional areas. They expanded upon and indeed went beyond the theme of instant messaging (IM) to employ the fundamental features of contextual messaging, presence, and request/response sequences, in a wide range of scenarios.

While these scenarios have in some ways been inward looking, they are natural progressions that originated inside the IM world and matured into applications and solutions that retain much of the messaging flavor. Let's consider what else Jabber has to offer as a messaging and routing mechanism.

This chapter explores some "outward looking" scenarios, to give you pointers and ideas for the future. With Demo::JBook, we consider the possibility of "Jabber without Jabber"—in other words, using Jabber as an infrastructure, in this case as a data store, without focus on any particular Jabber client or IM functionality. We also explore how Jabber is the perfect transport partner for procedure calls formalized in XML: JabberRPCRequester and JabberRPCResponder are scripts that exchange method calls and responses encoded in XML-RPC.

Using Jabber as a conduit to foreign systems is also a theme of this chapter. With ldapr, we build a script that reflects the hierarchy and contents of an LDAP data store, allowing that store to be navigated from a Jabber client. Finally, we look to the business world, employing Jabber in a tiny but crucial role as a conduit between SAP systems and their users.


A Simple Jabber-Based Address Book


With the availability of many different off-the-shelf Jabber clients and the use of these clients as generic tools to interact with diverse Jabber-based services, it's easy to lose sight of the fact that Jabber can also be used to contribute to infrastructure solutions. That is, applications and utilities can be built using the Jabber protocols, in conjunction with Jabber server-based services, without the need for a Jabber client.

By way of illustration, let's build a simple two-level address book using Jabber services. We'll call it Demo::JBook. We'll use this address book to look up details of our friends and colleagues while we're on the move. The ideal platform for this is going to be a web browser, in that it's accessible from personal workstations, airport web consoles, cybercafés, and personal digital assistants (PDAs) that offer access to the Internet.

The point of this illustration is not to show that there's a single solution to the problem of disconnected, incompatible, and unsynchronized directory information (because, despite any answers that you may get to the contrary—no such solution exists). Instead, the goal is to show that it's possible to make use of Jabber services and get to information stored and managed by those services without having to use a Jabber client.


Using the JUD and vCards


The two levels in our address book are going to reflect two distinct (but related) mechanisms in Jabber. We're going to base our address book on the Jabber User Directory (JUD) component and supply further information, in a "drill-down" action, using vCards.


The JUD
Our address book will act as a query frontend for a user directory in
the form of a JUD. It doesn't matter which JUD we use; obviously,
that depends on how the application is to be deployed. On one hand, it
might be appropriate to point it at your company's internal JUD, if
you have one. On the other hand, it might also be just as appropriate
to point it at one of the larger public JUDs, such as the one
connected to the Jabber server running on jabber.org (which is
users.jabber.org).
vCards
Every Jabber entity—users, components, and servers—has the potential
to have a vCard. We saw in Chapter 4 that the Jabber server itself,
and many of the components connected to it, had a vCard definition.
While the vCard standard is still fluid, the implementation within
Jabber, as described in Section 6.5.1 in Chapter 6, is enough to be
useful. The key to the application is that both the Jabber mechanisms
that it relies upon—the JUD and vCards—can be accessed independently
of the availability of the users that the information stored in those
mechanisms represents. The JUD runs as an independent component and
manages the directory information using its own data store. With the
default JUD and XML Database (XDB) component configurations, this data
store will be in the Jabber server's spool directory in a file called
jud/global.xdb.

Users interact with the JUD to manage their information, using IQ elements qualified by the jabber:iq:register namespace. User vCard information is also stored at the server side, and users manage the vCard contents using IQ elements qualified by the vcard-temp namespace. Again, with the default configuration, the vCard information, stored along with the rest of the user data relevant to the Jabber server in user-specific spool files, will be held in the Jabber server's spool directory in files called [hostname]/[user].xml.


What Demo::JBook Will Do


The JUD can be queried using a normal Jabber client. In Figure 10-1, we can see the search form of Jabber Instant Messenger (JIM).


Searching the JUD: the search form


</code> The search fields are not fixed; instead, they're dynamically generated, according to the result of an initial IQ-get in the jabber:iq:search namespace. Just as an IQ-get in the jabber:iq:register namespace (as illustrated in Section 8.3) is used for registering users, the jabber:iq:search namespace is is used for search requests. This is illustrated in Example 10-1, where an IQ-get is sent to the JUD at jud.gnu.mine.nu.


An IQ-get to a JUD in the jabber:iq:search namespace

SEND: <iq type="get" id="9138" to="jud.gnu.mine.nu"> <query xmlns="jabber:iq:search"/> </iq>

RECV: <iq from='jud.gnu.mine.nu' id='9138' to='qmacro@jabber.org/study' type='result'> <query xmlns='jabber:iq:search'> <first/> <last/> <instructions> Fill in a field to search for any matching Jabber users. </instructions> </query> </iq> In response to the IQ-get request, the JUD sends back a list of fields with which the directory can be searched, along with some simple instructions.

The actual search follows the same registration pattern (using the jabber:iq:register namespace) that we saw in Section 8.3. After receiving a list of possible search fields, a search request is made with an IQ-set, as shown in Example 10-2. The results, returned in an IQ-result from the JUD, are listed with each entry contained in an <item/> tag. The search results are shown in Figure 10-2.


[[image:title=Searching the JUD: the search results|image=0596002025-jab_1002.png</code>

The JUD search request and response

SEND: <iq type="set" id="2627" to="users.jabber.org"> <query xmlns="jabber:iq:search"> <last>adams</last> </query> </iq>

RECV: <iq from='users.jabber.org' id='2627' to='qmacro@jabber.org/study' type='result'> <query xmlns='jabber:iq:search'> <item jid='qmacro@jabber.org'> <nick>qmacro</nick> <first>DJ</first> <email>dj.adams@gmx.net</email> <last>Adams</last> </item> <item jid='qmacro@jabber.com'> <nick>dj</nick> <first>DJ</first> <email>dj.adams@gmx.net</email> <last>Adams</last> </item> <item jid='joseph@gnu.mine.nu'> <nick>joseph</nick> <first>Joseph</first> <email>joseph@pipetree.com</email> <last>Adams</last> </item>

         ... (more items) ...
       </query> </iq>


Searching a JUD


The first level that we'll build into Demo::JBook is the ability to query a JUD. The address book is to be browser-based, so we'll generate HTML on the fly according to the search fields we receive in response to our IQ-get. It should look something like that shown in Figure 10-3.


JUD search form in HTML


</code> The items returned in the search results don't really provide much information:


<item jid='joseph@gnu.mine.nu'> <nick>joseph</nick> <first>Joseph</first> <email>joseph@pipetree.com</email> <last>Adams</last> </item> Here, we have the user's first and last names, his nickname, and his email address. One of the functions of the JUD is to determine which fields are storable.


{{Sidebar|JUD FieldsThe fields in a JUD that are used to store the directory data are determined either by a hardcoded list in the JUD source itself or a configurable list that's stored and maintained separate from the JUD code. The list fields allowed for searching a JUD may or may not be used for storing the information; it may be a subset (it makes no sense, of course, for it to be a superset). For example, if the fields used to store information in a JUD are:


<name/> <email/> <first/> <last/> <nick/> <text/> it may be that the fields available for searching may be just:


<first/> <last/> as shown in Example 10-1.

In case you're wondering how to discover the fields that can be used to populate the information in the JUD, an IQ-get can be used in the jabber:iq:register namespace to qualify a registration conversation with the JUD, which is where the information is "registered," or stored. This is shown in Example 10-3.

</code>

An IQ-get to a JUD in the jabber:iq:register namespace

SEND: <iq type='get' to='jud.gnu.mine.nu'> <query xmlns='jabber:iq:register'/> </iq>

RECV: <iq from='jud.gnu.mine.nu' to='qmacro@jabber.com/study' type='result'> <query xmlns='jabber:iq:register'> <nick/> <first/> <email/> <last/> <text/> <name/> <instructions> Fill in all of the fields to add yourself to the JUD. </instructions> </query> </iq> We can see in Figure 10-4 how the results will typically be rendered.


[[image:jab_1004.png|JUD search results as rendered in Demo::JBook|center|350 px
</code>


Retrieving vCard information


As well as registering with a JUD, it's possible that a user has maintained more information about himself—in his vCard. Depending on the Jabber client used, various user information can be stored in a personal vCard, which is stored on the server side. In Figure 10-5, we see the JIM client's vCard maintenance window, titled User Profile.

The result of entering information and clicking the OK button in Figure 10-5 can be seen in Example 10-4, where an IQ-set in the vcard-temp namespace is made to store the information (some of the vCard tags have been omitted to keep the example short). Notice how no to attribute is specified in the IQ-set and how the result appears to come from the sender (qmacro@jabber.com/study). The storage of personal—user-specific—vCard information is a function of the Jabber Session Manager (JSM), which is where the <iq/> element will be routed automatically, as it is coming in over a client connection (defined by the jabber:client stream-level namespace). This is further discussed in Section 5.4.3.1.


[[image:jab_1005.png|Updating personal vCard information with JIM|center|350 px
</code>

Setting vCard information in the vcard-temp namespace

SEND: <iq type="set"> <VCARD version="3.0" xmlns="vcard-temp"> <N> <GIVEN>DJ</GIVEN> <FAMILY>Adams</FAMILY> <MIDDLE/> </N> <NICKNAME>qmacro</NICKNAME> <EMAIL> <INTERNET/> <PREF/> dj.adams@gmx.net </EMAIL>

         ...
       </VCARD> </iq>

RECV: <iq type='result' from='qmacro@jabber.com/study' to='qmacro@jabber.com/study'/> As well as being storable, the information in a personal vCard is also retrievable by anyone, anytime. The idea of a personal vCard is that the information it contains is permanently available. Because the vCard data is stored server side, it can be retrieved anytime the user is online.

The key (literally and metaphorically) of each of the search result items returned is a JID. We can see this in Example 10-2, where each <item/> tag has a jid attribute. You won't be surprised to know that the key to accessing someone's vCard is his JID too. So we have a great way for Demo::JBook to jump from level 1, which displays JUD search results, to level 2 by retrieving and displaying a vCard via the JID. The results of jumping from a JUD result entry to a vCard display for the selected user, via the JUD, can be seen in Figure 10-6.


[[image:jab_1006.png|A vCard as displayed in Demo::JBook|center|350 px-</code>


Using Demo::JBook as an Apache Handler


The Demo::JBook application is going to exist as an Apache mod_perl handler, that is, we'll use the power of Perl's integration into the Apache web server to write an Apache module in Perl. You can find out more about mod_perl at http://perl.apache.org.

Being a mod_perl module, Demo::JBook exists in the form of a Perl module and is configured in Apache to service calls to http://[hostname]/jbook. The configuration can be comfortably placed in the main Apache configuration file, httpd.conf, or, as is common in mod_perl installations, in an extra file usually called perl.conf, which is linked to httpd.conf as follows:


<IfModule mod_perl.c> Include conf/perl.conf </IfModule> The configuration for Demo::JBook is placed in the perl.conf file, as shown in Example 10-5.


Configuring the module as a handler in Apache

require conf/startup.pl

...

<Location /jbook> SetHandler perl-script PerlHandler Demo::JBook </Location> The PerlHandler directive refers to the Demo::JBook module, which is the JBook.pm file that exists in the Demo/ directory. The module will be invoked to handle calls to the relative URL /jbook. You can add the location of the Demo::JBook module to mod_perl's list of directories in the BEGIN section of the startup.pl script, in the conf/ directory, like this:


BEGIN { use Apache (); use lib '[the directory location of Demo::JBook]'; }


The Demo::JBook Script


Before taking the Demo::JBook script apart, let's have a look at the script in its entirety, shown in Example 10-6. Written in Perl, Demo::JBook uses the Jabber::Connection library.


The Demo::JBook script, written in Perl

package Demo::JBook;

use strict;

use Jabber::Connection; use Jabber::NodeFactory; use Jabber::NS qw(:iq
:misc);

use constant SERVER   => 'gnu.mine.nu'; use constant USER     =>
'jbook'; use constant PASS     => 'pass'; use constant RESOURCE =>
'jbook'; use constant JUD      => 'jud.gnu.mine.nu';


sub handler {

  my $r = shift; my @a = $r->args;

  my $nf = Jabber::NodeFactory->new;

  $r->content_type('text/html'); $r->send_http_header;

  $r->print("<html><head><title>JBook</title>
  </head><body>"); $r->print("<h1><a
  href="/jbook">JBook</a></h1>");

  # Connect to the Jabber server
  my $c = Jabber::Connection->new(server => SERVER); unless
  ($c->connect) { $r->print("Sorry, no connection to Jabber
  available at ".SERVER); $r->print("</body></html>");
  return;
  }

  $c->auth(USER, PASS, RESOURCE);


  # No arguments: Instructions
  if (!@a) {

    # Construct IQ-get in iq:search namespace
    my $iq = $nf->newNode('iq'); $iq->attr('to', JUD);
    $iq->attr('type', IQ_GET); $iq->insertTag('query', NS_SEARCH);

    # Send the IQ-get
    my $result = $c->ask($iq);

    if ($result->attr('type') eq IQ_ERROR) { $r->print("sorry, no
    connection to JUD available at ".JUD);
    $r->print("</body></html>"); $c->disconnect;
    return;
    }

    my $info = $result->getTag(, NS_SEARCH);

    # Display the results in HTML
    $r->print("<p><strong>".JUD."</strong></p&gt
    ;\n");
    $r->print("<p>".$info->getTag('instructions')->data."
    </p>\n"); $r->print("<form><table>\n"); foreach
    my $field ($info->getChildren) { next if $field->name eq
    'instructions'; $r->print("<tr>");
    $r->print("<td>".ucfirst($field->name)."</td>");
    $r->print("<td><input type="text'
    name="".$field->name.""></td>");
    $r->print("</tr>\n");
    }
    $r->print("<tr><td></td><td><input
    type="submit"></td></tr>\n");
    $r->print("</table></form>\n");

  }


  # Multiple arguments: JUD lookup
  elsif (scalar @a > 1) {

    # Treat the arguments as a hash
    my %a = @a;

    # Construct an IQ-set
    my $iq = $nf->newNode('iq'); $iq->attr('to', JUD);
    $iq->attr('type', IQ_SET); my $query = $iq->insertTag('query',
    NS_SEARCH);

    while (my($name, $val) = each(%a)) {
    $query->insertTag($name)->data($val) if $val;
    }

    # Make call
    my $result = $c->ask($iq);

    if ($result->attr('type') eq IQ_ERROR) { $r->print("sorry,
    cannot query JUD"); $r->print("</body></html>");
    $c->disconnect; return;
    }

    my $info = $c->ask($iq)->getTag(, NS_SEARCH);

    my $items = 0;

    $r->print("<p><strong>".JUD."</strong></p&gt
    ;\n"); $r->print("<table border="1">\n");

    foreach my $item ($info->getChildren) {

      # Heading
      unless ($items) { $r->print("<tr>"); foreach my $tag
      ($item->getChildren) {
      $r->print("<th>".ucfirst($tag->name)."</th>");
        }
        $r->print("</tr>\n");
      }

      $r->print("<tr>"); my $flag = 0; foreach my $tag
      ($item->getChildren) { unless (length($tag->data) == 0 or
      $flag++) { $r->print("<td><a
      href="/jbook?".$item->attr('jid')."">");
      $r->print($tag->data."</a></td>");
        }
        else { $r->print("<td>".$tag->data."</td>");
        }
      }
      $r->print("</tr>\n");

      $items++;
    }
    $r->print("</table>\n");

    $r->print("<p>$items results found</p>");

  }


  # Single argument: vCard lookup
  else {

    # Construct query
    my $iq = $nf->newNode('iq'); $iq->attr('to', $a[0]);
    $iq->attr('type', IQ_GET); $iq->insertTag('vcard', NS_VCARD);

    # Make call and retrieve results
    my $result = $c->ask($iq);

    if ($result->attr('type') eq IQ_ERROR)  { $r->print("sorry,
    cannot retrieve vCard for $a[0]");
    $r->print("</body></html>"); $c->disconnect;
    return;
    }

    my $vcard = $result->getTag(, NS_VCARD);

    print ("<strong>$a[0]</strong>\n");

    # Display each of the top-level tags if they contain data
    foreach my $tag ($vcard->getChildren) { print
    "<br/>".$tag->name." : ".$tag->data."\n" if
    $tag->data;
    }

  }

  $r->print("</body></html>"); $c->disconnect;

  return;

}

1;


Taking Demo::JBook Step by Step


Now that we've got a hold on the scope and scale of Demo::JBook, let's take the script apart—step by step—to see how it works.


Declarations


Because Demo::JBook is an Apache handler, it exists as a Perl module—hence the package declaration at the top of the file:


package Demo::JBook;

use strict;

use Jabber::Connection; use Jabber::NodeFactory; use Jabber::NS qw(:iq

misc);

use constant SERVER => 'gnu.mine.nu'; use constant USER => 'jbook'; use constant PASS => 'pass'; use constant RESOURCE => 'jbook'; use constant JUD => 'users.jabber.org'; This code exists within the Demo::JBook package that was declared as the handler for the http://[hostname]/jbook location, shown in Example 10-5. We're going to make full use of the Jabber::Connection library and bring in all three of its modules for managing the Jabber server connection, for dispatching elements that arrive (Jabber::Connection), for building and manipulating elements (Jabber::NodeFactory), and using common Jabber programming constants (Jabber::NS). In the case of Jabber::NS, we need only a few namespaces to manage our JUD and vCard queries, so the :iq and :misc tags will be used to refer a collection of constants in Jabber::NS.

The constants SERVER, USER, PASS, and RESOURCE define the connection to the Jabber server. This connection doesn't have to be made to the JUD that will be queried by Demo::JBook. By way of illustration, we have jabber.org's JUD (users.jabber.org) specified as the value for the JUD constant. For the purpose of this example, this will be the JUD that Demo::JBook will query.

You can use the reguser script, described in Section 7.4, to create the Demo::JBook user. See Section 8.2.1.1 for an example of how this can be done.


General handler preparation


Following the script's declarations, it's time to define the handler function that Apache will call to handle incoming requests to the http://[hostname]/jbook location. The name of the handler must be handler():


sub handler {

 my $r = shift; my @a = $r->args;
 my $nf = Jabber::NodeFactory->new;
 $r->content_type('text/html'); $r->send_http_header;
 $r->print("<html><head><title>JBook</title>
 </head><body>"); $r->print("<h1><a
 href="/jbook">JBook</a></h1>");
 # Connect to home Jabber server
 my $c = Jabber::Connection->new(server => SERVER); unless
 ($c->connect) { $r->print("Sorry, no connection to Jabber
 available at ".SERVER); $r->print("</body></html>");
 return;
 }
 $c->auth(USER, PASS, RESOURCE);

The mod_perl mechanism hands the handler() function an argument that is stored in a variable called $r. This is the HyperText Transfer Protocol (HTTP) request that has been made, which is handled by the function.

Calling the args() method on our request object gives us a list of arguments. These arguments follow the question mark in a typical HTTP GET request. In Figure 10-3, the URL in the Location bar is http://www.pipetree.com/jbook. In this URL, there is neither a question mark nor any arguments. The assignment to @a here:


my @a = $r->args; would leave @a empty.


{{Note|The first part of the URL containing the hostname is truncated by the size of the browser window.

</code> However, if the URL in the Location bar (shown in Figure 10-4) contained question marks and/or arguments, such as:


http://www.pipetree.com/jbook?name=&first=&last=adams&amp

nick=&email=

the arguments that @a would receive follow the question mark and are separated in pairs with ampersands and further separated with equals signs. So here, @a would receive the arguments:


name, (blank), first, (blank), last, adams, nick, (blank), email, (blank) In the final URL example, shown in Figure 10-6:


http://www.pipetree.com/jbook?dj@gnu.mine.nu the array received by @a is just a single element:


dj@gnu.mine.nu The content of @a defines which stage of the script takes the appropriate action. Queries will be generated in the form of IQ-gets and IQ-sets to retrieve information from the JUD as well as to retrieve personal vCards. To do this, we need to create an instance of a node factory, so we can build elements:


my $nf = Jabber::NodeFactory->new; After retrieving the request arguments and creating a node factory instance, it's time to generate some HTML that will be common to all of Demo::JBook's features. The print() method will be used on the request object $r to send a response back to the requesting web browser:


$r->print("<html><head><title>JBook</title>& lt;/head><body>"); $r->print("<h1><a href="/jbook">JBook</a></h1>"); Next, we need to create a connection to the Jabber server for each request:


# Connect to home Jabber server my $c = Jabber::Connection->new(server => SERVER); unless ($c->connect) { $r->print("Sorry, no connection to Jabber available at ".SERVER); $r->print("</body></html>"); return;

 }
 $c->auth(USER, PASS, RESOURCE);

It will be much more efficient to create a persistent connection that could be used to serve further requests. One way to do this is to fork a daemon that connects to the Jabber server, acting as a proxy for this script. Rather than make direct requests to the Jabber server, Demo::JBook is used to send the IQ elements to the daemon, which in turn uses its persistent Jabber connection to make queries on Demo::JBook's behalf. A setup like this is shown in Figure 10-7.


Using a persistent connection to Jabber

</code>


State 1: Build the JUD query form


Based upon how many arguments we have in the @a array, as described earlier, we can build the response, which will represent one of three states:

  • JUD query form
  • JUD query results
  • vCard display If Demo::JBook is called without arguments (i.e.,

http://[hostname]/jbook), which we test for like this:


if (scalar @a == 0) { then we want to build and present the JUD search form. This form—the fields that the form consists of—will be specific to the particular JUD that will be searched.

We construct an IQ-get to send to the JUD to ask for a list of search fields and instructions. What we're looking for is something like the IQ-get shown in Example 10-1, like this:


SEND: <iq type="get" to="users.jabber.org"> <query xmlns="jabber:iq:search"/> </iq> To construct this, we start with a new node (element) created by using the node factory:


my $iq = $nf->newNode('iq'); $iq->attr('to', JUD); $iq->attr('type', IQ_GET); $iq->insertTag('query', NS_SEARCH); In previous recipes, we've called methods in the Jabber libraries (Jabberpy, JabberBeans, Net::Jabber, and Jabber::Connection) to send an element to the Jabber server. Typically, such a method is called a send() method. Here, we don't use a send() method. Instead, we use Jabber::Connection's ask(). Like Net::Jabber's SendAndReceiveWithID() method, and Jabberpy's method of the same name, ask() not only sends the element to the Jabber server, it waits for a reply.

A reply—in Jabber terms—is a response in the form of an element that comes back along the stream, with a matchingidattribute. In this case, we're making an IQ-get and are expecting a response back from the recipient of that IQ-get. One way of expecting and handling the response would be to use a predefined callback specified for <iq/> elements and send our IQ-get with the send() method. However, this means that the script receives the response in an element callback that's somewhat independent of our call to send(). As well as catching the response in the callback, we also need some way of matching it up with the original request. Not to mention getting back on track with the flow of execution that represented the logical sequence of events that we were in the middle of following when we called send().

There's an easier way, if we want to make a request and wait for the response to that request before continuing on in the script. This way makes use of the ask() method.

What the ask() method does is avoid the need to catch responses in callbacks and match them up with their originating requests. It send()s the element to the Jabber server and blocks until an element with a matching id attribute value is received. Other elements that might be received while waiting for the response are duly dispatched as normal. It's not as if elements get queued up if the response takes a moment or two to arrive. When the matching element arrives on the stream, it is passed directly back to the caller of the ask() method, in the same form as if it had been handed to a callback—in object form, as an instance of a Jabber::NodeFactory::Node object, in this case.


my $result = $c->ask($iq); So here, $result receives the response to the <iq/> element just sent. This response will look something like this:


RECV: <iq from='users.jabber.org' id='43' to='jbook@gnu.mine.nu/jbook' type='result'> <query xmlns='jabber:iq:search'> <nick/> <first/> <email/> <last/> <instructions> Fill in a field to search for any matching Jabber users. </instructions> </query> </iq> It's addressed to the JID that the script is using, jbook@gnu.mine.nu/jbook. But hold on—there's something that doesn't look quite right here, that is, when compared to the example of the IQ-get we just sent. Indeed—there's an id attribute in the response. We didn't specify one in the Perl code that built the <iq/> element. The ask() method did it for us. Knowing that it's going to have to check the id attribute on every incoming element to find a match for the element it's just sent off for us, the ask() method makes sure that the outgoing element actually has an id attribute. If it doesn't, it adds one, giving it a unique value. That way, it stands a fighting chance of returning to the caller something this side of the end of time.


{{Note|Talking of time, if you're uneasy about calling blocking functions in general, you can always set an alarm() to interrupt the call after a certain length of time.

</code> For more information on matching requests and responses, see Section 2.5 in Chapter 2.

Now let's move on to the response to IQ-get, which we now have in $result. While we're expecting an IQ-result in response to IQ-get, the request might not have been succesful, and we simply bail out gracefully if it isn't, ending our connection with the Jabber server:


if ($result->attr('type') eq IQ_ERROR) { $r->print("Sorry, no connection to the JUD available at ".JUD); $r->print("</body></html>"); $c->disconnect; return;

   }

Otherwise, we're expecting a result, containing the search fields and some instructions. These will be contained within the query tag qualified by the jabber:iq:search namespace (the tag is usually called query, but here, as elsewhere in the script, we're not taking any chances and are looking for "the first (hopefully the only!) occurrence of a child tag qualified by the jabber:iq:search namespace."


my $info = $result->getTag(, NS_SEARCH); An HTML form is built from the instructions and the search fields; the instructions are retrieved with:


$info->getTag('instructions')->data which retrieves the <instructions/> tag and extracts its contents.

The getChildren() method is called upon our <query/> tag to discover what fields are available. For all those fields, barring the "instructions" one, we create an input text field:


# Display the results in HTML $r->print("<p><strong>".JUD."</strong></p>\n" ); $r->print("<p>".$info->getTag('instructions')->data."< /p>\n"); $r->print("<form><table>\n"); foreach my $field ($info->getChildren) { next if $field->name eq 'instructions'; $r->print("<tr>"); $r->print("<td>".ucfirst($field->name)."</td>"); $r->print("<td><input type="text' name="".$field->name.""></td>"); $r->print("</tr>\n");

   }
   $r->print("<tr><td></td><td><input
   type="submit"></td></tr>\n");
   $r->print("</table></form>\n");
 }

Once the form has been built, the work for this stage is complete—the form is relayed to the user, who will submit a completed form, thereby invoking the next state.


State 2: Query the JUD


The submission of the HTML form will cause a number of name/value pairs to be passed as part of the HTTP GET request. These names and values are captured into the @a array as described earlier. If we have more than one entry in @a, we know it's a form submission and must respond to that by querying the JUD and returning the results. In this case, as we know that the contents of @a are name/value pairs, we can view those contents as a hash, using a new variable %a:


elsif (scalar @a > 1) { my %a = @a; Now, %a will contain entries where the keys are the names of the search fields and the values are the values entered in the form.

In the same way that we constructed an IQ-get to query the JUD for the search fields and instructions, we construct an IQ-set to perform the actual query:


my $iq = $nf->newNode('iq'); $iq->attr('to', JUD); $iq->attr('type', IQ_SET); my $query = $iq->insertTag('query', NS_SEARCH);

Using the information in %a, we insert tags for each of the search fields for which a value was specified in the form. For example, if only the value adams was specified, in the field representing the <last/> search field, as shown in Figure 10-3, we would only want to insert:


<last>adams</last> as a child of the IQ-set's <query/> tag:


while (my($name, $val) = each(%a)) { $query->insertTag($name)->data($val) if $val;

   }

Also in a similar way to handling state 1, we make the call by using the ask() method; the response will be received into the $result variable as an object representation of the IQ-result (or IQ-error) element:


my $result = $c->ask($iq); We deal simply with any error situation:


if ($result->attr('type') eq IQ_ERROR) { $r->print("Sorry, cannot query the JUD"); $r->print("</body></html>"); $c->disconnect; return;

   }

otherwise proceeding to extract the <query/> tag from the search result. This tag will contain the <item/>s representing the JUD entries found to match the search criteria submitted (see the response in Example 10-2):


my $info = $c->ask($iq)->getTag(, NS_SEARCH); We want to display a simple table of results, with each table row representing an <item/>:


my $items = 0;

   $r->print("<p><strong>".JUD."</strong></p&gt
   ;\n"); $r->print("<table border="1">\n");
   foreach my $item ($info->getChildren) {

During the first iteration of the foreach loop, that is, on our first child tag of <query/>, we take the opportunity to write out a heading row, using the HTML <th/> (table heading) tags:


unless ($items) { $r->print("<tr>"); foreach my $tag ($item->getChildren) { $r->print("<th>".ucfirst($tag->name)."</th>");

       }
       $r->print("</tr>\n");
     }

The main part of our loop translates the information found in each <item/> tag:


<item jid='joseph@gnu.mine.nu'> <nick>joseph</nick> <first>Joseph</first> <email>joseph@pipetree.com</email> <last>Adams</last> </item> into HTML table rows, with one table cell (<td/>) for each item field:


$r->print("<tr>"); my $flag = 0; foreach my $tag ($item->getChildren) { unless (length($tag->data) == 0 or $flag++) { $r->print("<td><a href="/jbook?".$item->attr('jid')."">"); $r->print($tag->data."</a></td>");

       }
       else { $r->print("<td>".$tag->data."</td>");
       }
     }
     $r->print("</tr>\n");

In this section of the code, we also make the link between the first and second level of the address book. The JID, specified in each <item/> tag's jid attribute, is the key to the JUD entry that the item represents and also the key to the vCard of the user that has that JID. For each of the item lines in the table, we need to build a selectable link to lead the user to the second level, which allows him to view the vCard. This is what we want each link to look like:


http://www.pipetree.com/jbook?dj@gnu.mine.nu That is, a single argument, representing the JID, specified after the question mark. The condition:


unless (length($tag->data) == 0 or $flag++) serves to ensure that the link is made on a single, nonempty field in the JUD item. When registering with a standard JUD, none of the fields are compulsory, so it's quite possible for there to be missing values returned in the search results. So we want to make sure that the <a href="...">...</a> link that we build actually surrounds some value; otherwise, it wouldn't be clickable. The $flag variable just ensures we build only one link and not a link for every nonempty field.

Finally, the number of items found is displayed with:


$items++;

   }
   $r->print("</table>\n");
   $r->print("<p>$items results found</p>");
 }


State 3: Retrieve a vCard


If we receive a single argument in the request, we'll take it to be a JID, passed from the link in the table build in the previous state, and immediately build an IQ-get to retrieve the vCard. The JID is to be found in the first element in the @a array—$a[0].


else {

   my $iq = $nf->newNode('iq'); $iq->attr('to', $a[0]);
   $iq->attr('type', IQ_GET); $iq->insertTag('vcard', NS_VCARD);

Notice how the name of the query tag in this query is not query, but vcard. See Section 6.5.1 in Chapter 6 for details.

The retrieval query is in the form of an IQ-get, rather than an IQ-set. As we needed to send information in our JUD query (the search criteria), an IQ-set was appropriate. Here, an IQ-get is appropriate as we're not including any information to qualify our request; all we need to send is this:


<iq type='get' to='dj@gnu.mine.nu'> <vcard xmlns='vcard-temp'/> </iq> After sending the <iq/> element, waiting for the response, and checking for any errors, we extract the vCard detail and display it:


my $result = $c->ask($iq);

   if ($result->attr('type') eq IQ_ERROR)  { $r->print("Sorry,
   cannot retrieve the vCard for $a[0]");
   $r->print("</body></html>"); $c->disconnect;
   return;
   }

The structure of the vCard namespace is rather complicated and long-winded, and it's common for many of the fields to remain unfilled. So to keep the script simple, we're going to display all the top-level fields that aren't empty:


my $vcard = $result->getTag(, NS_VCARD);

   print ("<strong>$a[0]</strong>\n");
   foreach my $tag ($vcard->getChildren) { print
   "<br/>".$tag->name." : ".$tag->data."\n" if
   $tag->data;
   }
 }


General handler close


Once we've dealt with the possible states, we send the closing HTML statements common to all three of them, and disconnect from the Jabber server:


$r->print("</body></html>"); $c->disconnect;

 return;

At this stage, there's nothing more for the module to do. Having discerned the state from the arguments in the URL (and thereby the appropriate action) and having carried out that action, the module ends, handing control back to its mod_perl host. Remembering that Demo::JBook is an Apache handler in the form of a Perl module, we need to ensure that the module itself returns a true value (as with any Perl module):


}

1;


Notes for Improvement


The Demo::JBook script is merely an example. On top of tightening up the error and exception handling, there are a few other things that you might want to consider doing to improve upon it:


Jabber connectivity
As mentioned already, you'll probably want to improve the connection
efficiency to the Jabber server by holding a socket open and sharing
this connection across multiple calls to the handler.
Choice of JUD
The JUD to be queried is fixed; you may prefer to allow the user to
select which JUD will be searched. What's more, selection of more than
one JUD would allow a powerful search across public Jabber user
directories.
Key handling
We've seen how a JUD is queried in Example 10-2. Some JUDs use the
simple key-based security and pass an additional <key/>
tag containing random data—a sort of session key, as described in
Section 6.2.11 and Section 6.2.13. Any <key/> tag
received from the JUD in response to an IQ-get must be sent back
verbatim to the JUD in the subsequent IQ-set. Otherwise the search
will fail, and you'll get a response similar to that shown in Example
10-7.
Visual impact
Last but not least, the visual impact of the end result as shown here
(in Figure 10-3, Figure 10-4, and Figure 10-6) lacks a certain
something. You might want to do something about that—give it a grander
design, make it more pleasing, or at least interesting, to the eye.
The HTML has been kept deliberately basic in this recipe, so as not to
cloud the real theme of "Jabber without Jabber."

Failure to return a key could cause a search to fail

RECV: <iq from='users.jabber.com' id='jud33' to='jbook@gnu.mine.nu/jbook' type='error'> <query xmlns='jabber:iq:search'> <error code='405'>Keys do not match.</error> </query> </iq>

XML-RPC over Jabber


XML-RPC is an easy way to get software that's running on different operating systems to be able to make and respond to procedure calls (the "RPC" part of the name stands for "Remote Procedure Call") over the Internet.

The basis of XML-RPC is straightforward and is described at XML-RPC's home page (http://www.xml-rpc.com). The procedure calls, each consisting of the name of the procedure (or method) to call and a set of arguments to go with that call and the corresponding responses, each consisting of a set of results, are encoded in an XML-based format. The requests and responses, so encoded, are exchanged over HTTP, carried as the payloads of POST requests.

Example 10-8 shows a typical request in XML-RPC encoding. It's calling a procedure called examples.getStateName, and passing a single integer parameter with the value 41.


An XML-RPC request

<?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall> Example 10-9 shows a typical response to that request. The response consists of a single string value "South Dakota."


An XML-RPC response

<?xml version="1.0"?> <methodResponse> <params> <param> <value><string>South Dakota</string></value> </param> </params> </methodResponse> The choice of the word "payload" to describe the encoded requests and responses is significant: each request, headed with an XML declaration (<?xml version="1.0"?>) and encapsulated as a single tag (<methodCall/>), and each response, also headed with an XML declaration and encapsulated as a single tag (<methodResponse/>), are succinct, fully formed, and complete parcels that have meaning independent of their HTTP carrier.

While the XML-RPC specification stipulates that these parcels be carried over HTTP, we could take advantage of the power and simplicity of the encoding and carry procedure calls and responses over Jabber.



Jabber-RPC


Jabber-RPC is the name given to the marriage of encoding from the XML-RPC specification and Jabber as the transport mechanism. As well as building upon a stable specification, Jabber-RPC brings advantages of its own to the world of procedure calls over the Internet. Many of the potential XML-RPC responders are HTTP servers behind corporate firewalls or one-way Network Address Translation (NAT) mechanisms and are therefore unreachable from the Internet. Substituting Jabber as a transport gives calls a better chance of reaching their destination. If a Jabber-RPC responder—a program that connects to a Jabber server, is addressable by a JID, and can receive (and respond to) XML-RPC-encoded calls—is connected to a Jabber server visible to the Internet, then request calls have to make it only to that Jabber server, and internal packet routing within the server will allow the parcels to reach their destinations behind the firewall, whether those destinations are client-based or component-based responders.

It should be clear by now that the idea of Jabber-RPC is to transport the XML-RPC-encoded parcels in an extension, an attachment, to an IQ element. Just as the details for a search attempt (for example of a Jabber User Directory) are carried in an IQ-set extension qualified by the jabber:iq:search namespace (as shown in Example 10-2), so the Jabber-RPC method calls are carried in an IQ-set extension qualified by a namespace of its own. This namespace is a new one and is jabber:iq:rpc.[1]

Similarly, as the results of a JUD search are returned in an IQ-result, so Jabber-RPC method responses are returned in an IQ-result. Example 10-10 shows a simple Jabber-RPC-based method call and response, using the same XML-RPC-encoded parcels as shown in Example 10-8 and Example 10-9.


A Jabber-RPC request/response conversation

SEND: <iq type='set' to='responder@company-a.com/jrpc-server' id='1'> <query xmlns='jabber:iq:rpc'> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall> </query> </iq>

RECV: <iq type='result' to='requester@company-b.com/jrpc-client' from='responder@company-a.com/jrpc-server' id='1'> <query xmlns='jabber:iq:rpc'> <methodResponse> <params> <param> <value><string>South Dakota</string></value> </param> </params> </methodResponse> </query> </iq> It's clear that the parcels of XML-RPC encoding lend themselves very well to being transported in a meaningful way over Jabber and that Jabber's ultimate flexibility makes this possible.

The only major difference between the payloads as carried in an HTTP POST and the payloads as carried in an <iq/> element is that there's no XML declaration. It's not possible of course, when you consider the context of the IQ elements. They're document fragments in the XML stream between the requester (or responder) and the Jabber server. As explained in Section 5.3, these documents are fanfared with their own XML declaration, and any further declaration within the document is illegal from an XML point of view.


{{Sidebar|Jabber-RPC Requesters and RespondersA quick word about Jabber-RPC requesters and reponders. Connection to a Jabber server is possible in two main ways, as we've seen in the recipes so far: as a client via the JSM or as a component connected directly to the jabberd backbone. At the simplest level, all a Jabber-RPC requester or responder is, is something that makes a Jabber connection, sends and receives IQ elements, and uses a standard XML-RPC library to encode, decode, and service the requests and responses.

It doesn't matter whether the requesters and responders are built as clients or components. One could argue that the Jabber client model fits more naturally into the role of a requester, and the Jabber component model fits more naturally into the role of a responder, but this isn't a requirement. Indeed, if you're not the Jabber server administrator and don't have access to the server configuration (to be able to insert a <service/> stanza for a new component—see Section 9.3.1.1), building a Jabber-RPC responder as a Jabber client may be the path of least resistance.

</code>


Building a Requester and a Responder


Let's have a look at the power and flexibility of Jabber-RPC and how to build requesters and responders. We're going to build a client-based requester, in Python, and a client-based responder, in Java. There are two great XML-RPC library implementations for Python and Java that we'll use to do the legwork for us. Figure 10-8 shows what we want to build.

To keep things fairly simple, we'll just implement and call something similar to the getStateName() function already shown in the examples: getCountyName().


[[image:jab_1008.png|The Jabber-RPC requester and responder|center|350 px
</code>

The responder: JabberRPCResponder


We'll start up the Java client-based Jabber-RPC responder (imaginatively called JabberRPCResponder) specifying a handler class that is to service the XML-RPC-encoded method calls, get it to connect to a Jabber server, and listen for incoming Jabber-RPC requests. It will use the Helma XML-RPC library (at http://xmlrpc.helma.org) to service incoming requests using the handler class.

Being a Java script, we'll use the JabberBeans library. We'll need to extend the library's capabilities to handle extensions in the new jabber:iq:rpc namespace.


The requester: JabberRPCRequester


The Python client-based Jabber-RPC requester will also start up and connect to a Jabber server. We'll use a different server than the one the responder is connected to; after all, the whole point of XML-RPC and Jabber-RPC is to make the RPC world a smaller and bigger place at the same time, through the power of the Internet. We're going to use the Pythonware XML-RPC library (http://www.pythonware.com/products/xmlrpc/index.htm) to encode a getCountyName() request and decode the response.



JabberRPCResponder


Let's look at the Jabber-RPC responder first. There is a single script, called JabberRPCResponder, but there are also a number of supporting classes that we need. Let's take things one at a time.


The RPCHandler class


The Helma XML-RPC library implementation allows you to build XML-RPC responders independent of any particular transport by using an instance of the XmlRpcServer object, which represents an abstraction of an XML-RPC server. You can then construct a class—the handler class—containing your methods to be callable via XML-RPC. The calling of these methods is coordinated by the XmlRpcServer object, which you tell about your handler class using the addHandler() method.

This is what the handler class, called RPCHandler, looks like:


// RPCHandler: A class of XML-RPC-callable methods

public class RPCHandler {

 // Note: This is a "traditional" list of counties!
 private String county[] = {"Bedfordshire", "Berkshire",
 "Buckinghamshire", "Cambridgeshire", "Cheshire", "Cornwall",
 "Cumberland", "Derbyshire", "Devon", "Dorset", "Durham", "Essex",
 "Gloucestershire", "Hampshire", "Herefordshire", "Hertfordshire",
 "Humberside", "Huntingdonshire", "Kent", "Lancashire",
 "Leicestershire", "Lincolnshire", "London", "Middlesex", "Norfolk",
 "Northamptonshire", "Northumberland", "Nottinghamshire",
 "Oxfordshire", "Rutland", "Shropshire", "Somerset", "Staffordshire",
 "Suffolk", "Surrey", "Sussex", "Warwickshire", "Westmorland",
 "Wiltshire", "Worcestershire", "Yorkshire"};
 public RPCHandler() {}
 public String getCountyName(int c) { return county[c - 1];
 }

} The class has three elements:


The list of counties
The names of counties are stored in a simple array, county[].
The constructor
We don't need to do anything in the constructor,
RPCHandler(), as there's no requirement to manipulate objects
directly, so the constructor remains empty.
The single available method
All public methods in the class are available through the
XmlRpcServer object. Here we have a single method for the
purposes of this recipe: getCountyName() returns the name of
the county for the index given. When we examine the
JabberRPCResponder script (in Section 10.2.3.3) we'll see how the
XmlRpcServer object is instantiated and how this
RPCHandler class is used.


The IQRPC classes


JabberBeans deals with Jabber element extensions—<query/> and <x/> tags—using individual helper classes. We've seen this in the HostAlive recipe in Section 8.2, where the IQAuthBuilder class is used to construct an authorization extension:


<query xmlns='jabber:iq:auth'> <username>alive</username> ... </query> The helper classes are oriented around namespaces. Because we have a new namespace to deal with (jabber:iq:rpc), JabberBeans needs to have helper classes to handle extensions in that namespace.

We need a minimum of two helper classes. We need a class that represents an extension—a <query/> tag—in the jabber:iq:rpc namespace. If we are going to construct IQ elements containing such Jabber-RPC extensions, we also need a class to build those extensions. The class that represents the jabber:iq:rpc extension is called IQRPC, and the class that is the builder for the extensions is called IQRPCBuilder.

For an example we're already familiar with, look at the steps leading up to the authorization phase in the HostAlive script (and indeed our JabberRPCResponder script, which is shown in the next section); the authorization IQ element is constructed as follows:


InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iqAuthb = new IQAuthBuilder();

iqb.setType("set");

iqAuthb.setUsername(user); iqAuthb.setPassword(pass); iqAuthb.setResource(resource);

iqb.addExtension(iqAuthb.build()); If we bear in mind that this is what we want to end up with:


<iq type='set'> <query xmlns='jabber:iq:auth'> <username>...</username> <password>...</password> <resource>...</resource> </query> </iq> then we can understand what's going on:

  • The <query/> tag in the jabber:iq:auth

namespace is built with an authorization builder, iqAuthb, which is an instance of IQAuthBuilder.

  • Values for the tags within <query/>, such as <username/> and

<password/>, are set using methods belonging to that builder class.

  • The actual extension is generated with a call to the

build() method. Using addExtension(), the newly generated extension is added to the container representing the <iq/> element being constructed by the IQ builderiqb, an instance of InfoQueryBuilder.

Figure 8-3 shows the relationship between these builders and the things they create. (For a review of what's required to authenticate with a Jabber server, see Section 7.3.)

Although necessary, the IQRPC classes aren't the focus of this recipe and can be found in Appendix B. They are essentially modified copies of the classes for the jabber:iq:time namespace: IQTime and IQTimeBuilder. They just need to be compiled and made available in the $CLASSPATH. Putting them in the same directory as the JabberRPCResponder script will work fine.


The JabberRPCResponder script


Example 10-11 shows the JabberRPCResponder script in its entirety. In the next section we'll take it piece by piece.


The JabberRPCResponder Script, written in Java

import org.jabber.jabberbeans.*; import org.jabber.jabberbeans.Extension.*; import org.jabber.jabberbeans.util.JID; import java.net.InetAddress; import java.util.Enumeration; import java.io.*; import helma.xmlrpc.*;

public class JabberRPCResponder implements PacketListener { private String server = "gnu.mine.nu"; private String user = "server"; private String pass = "pass"; private String resource = "jrpc-server";

 private XmlRpcServer responder;
 private ConnectionBean cb;


 // Constructor
 public JabberRPCResponder() { responder = new XmlRpcServer();
 responder.addHandler("examples", new RPCHandler());
 }
 // Main program
 public static void main(String args[]) { JabberRPCResponder server =
 new JabberRPCResponder(); try { server.start();
   }
   catch (Exception e) { System.out.println("Cannot start server: " +
   e.toString());
   }
 }
 public void start() throws Exception { cb = new ConnectionBean();
 InetAddress addr;
   cb.addPacketListener(this);
   // Connect
   cb.connect(addr=InetAddress.getByName(server));
   // Authenticate
   InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iqAuthb
   = new IQAuthBuilder();
   iqb.setType("set");
   iqAuthb.setUsername(user); iqAuthb.setPassword(pass);
   iqAuthb.setResource(resource);
   iqb.addExtension(iqAuthb.build());

InfoQuery iq = (InfoQuery)iqb.build();

   cb.send(iq);
   // Send presence
   PresenceBuilder pb = new PresenceBuilder(); cb.send(pb.build());
 }


 // Packet listener interface:
 public void receivedPacket(PacketEvent pe) { Packet packet =
 pe.getPacket(); System.out.println("RECV:" + packet.toString());
   if (packet instanceof InfoQuery) { Enumeration e =
   ((InfoQuery)packet).Extensions(); if (e.hasMoreElements()) {
   Extension ext = (Extension)e.nextElement();
       String request = ext.toString(); String id =
       ((InfoQuery)packet).getIdentifier(); JID from =
       ((InfoQuery)packet).getFromAddress();
       ByteArrayInputStream bis = new
       ByteArrayInputStream(request.getBytes()); String result = new
       String(responder.execute(bis));
       String response = result; int pos = result.lastIndexOf("?>");
       if (pos >= 0) { response = result.substring(pos + 2);
       }
       IQRPCBuilder iqrpcb = new IQRPCBuilder();
       iqrpcb.setPayload(response);
       InfoQueryBuilder iqb = new InfoQueryBuilder();
       iqb.setType("result"); iqb.setIdentifier(id);
       iqb.setToAddress(from); iqb.addExtension(iqrpcb.build());
       InfoQuery iq; try { iq = (InfoQuery)iqb.build();
       }
       catch (InstantiationException ie) { System.out.println("Build
       failed: " + ie.toString()); return;
       }
       cb.send(iq);
     }
   }
 }
 public void sentPacket(PacketEvent pe) { Packet packet =
 pe.getPacket(); System.out.println("SEND:" + packet.toString());
 }
 public void sendFailed(PacketEvent pe) { Packet packet =
 pe.getPacket(); System.out.println("failed to send:" +
 packet.toString());
 }

}

Looking at JabberRPCResponder Step by Step


Now let's examine the JabberRPCResponder script step by step so we can see how it works:


import org.jabber.jabberbeans.*; import org.jabber.jabberbeans.Extension.*; import org.jabber.jabberbeans.util.JID; import java.net.InetAddress; import java.util.Enumeration; import java.io.*; import helma.xmlrpc.*; We need to bring in the jabberbeans classes as shown, as well as some core Java features that we'll see used in the script a bit later: an InetAddress to represent the Jabber server's hostname, an Enumeration interface to access the extensions in the incoming IQ elements, and java.io features for feeding the XML-RPC-encoded requests to the XmlRpcServer object. We also bring in the classes from the Helma XML-RPC library.


public class JabberRPCResponder implements PacketListener { private String server = "gnu.mine.nu"; private String user = "server"; private String pass = "pass"; private String resource = "jrpc-server";

 private XmlRpcServer responder;
 private ConnectionBean cb;

The definition of our JabberRPCResponder class looks similar to that of the HostAlive class in Section 8.2. However, rather than merely connecting to a Jabber server and sending packets off down the stream, we want to listen for incoming packets—in this case, IQ elements carrying jabber:iq:rpc-qualified payloads—and handle them. Accordingly, we specify that our main class implements PacketListener, a JabberBeans interface that Jabber clients use to receive notification of incoming packets. The interface describes three methods: receivedPacket(), sentPacket(), and sendFailed(). We'll define our receivedPacket() method, described later in this section, to catch and process the incoming Jabber-RPC requests.

We're going to use an XmlRpcServer object, in responder, to provide the translation services between our RPCHandler class that contains the methods we want to "expose," and the XML-RPC-encoded requests and responses.

Naturally, we also need a JabberBeans ConnectionBean, which we'll hold in cb.


public JabberRPCResponder() { responder = new XmlRpcServer(); responder.addHandler("examples", new RPCHandler());

 }

The JabberRPCResponder class constructor, JabberRPCResponder(), will be called in the main() method later in the script. It is used to create an instance of the Helma XmlRpcServer object, and associate our RPCHandler class with it, as a handler for method calls. The addHandler() method takes two arguments: the first is the method prefix name, such as examples in the methodName specification:


<methodName>examples.getCountyName</methodName> and the second is the object—an instantiation of our handler class—which contains the methods that the XmlRpcServer will use to "service" requests. In other words, the XmlRpcServer object will determine that a call to examples.getCountyName should be handled, as getCountyName, by the RPCHandler object.

The main() method is quite short:


public static void main(String args[]) { JabberRPCResponder server = new JabberRPCResponder(); try { server.start();

   }
   catch (Exception e) { System.out.println("Cannot start server: " +
   e.toString());
   }
 }

We instantiate our JabberRPCResponder object into server and call the start() method. Various functions in start() can raise exceptions; we take care of them all here with a simple catch clause and abort if necessary, rather than include all of start()'s functions here and have multiple try and catch statements:


public void start() throws Exception { cb = new ConnectionBean(); InetAddress addr;

   cb.addPacketListener(this);
   // Connect
   cb.connect(addr=InetAddress.getByName(server));
   // Authenticate
   InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iqAuthb
   = new IQAuthBuilder();
   iqb.setType("set");
   iqAuthb.setUsername(user); iqAuthb.setPassword(pass);
   iqAuthb.setResource(resource);
   iqb.addExtension(iqAuthb.build());

InfoQuery iq = (InfoQuery)iqb.build();

   cb.send(iq);
   // Send presence
   PresenceBuilder pb = new PresenceBuilder(); cb.send(pb.build());
 }

Most of the content of this start() function should be fairly familiar. We instantiate a ConnectionBean and will use that, in cb, throughout the script to send elements to the Jabber server. Our PacketListener is added as a listener to the ConnectionBean, causing a method of that interface (packetReceived()) to be invoked when an incoming element appears on the stream managed by that ConnectionBean.

In the same way as described in Section 8.2.2 in Chapter 8, we build our IQ element to authenticate, and send it to the server with the ConnectionBean's send() method. We also send an initial availability, in the form of a simple <presence/> element to the server.

Having connected to and authenticated with the server and sent initial availability, we can sit back and relax. All the subsequent activity will be initiated by the arrival of IQ elements on the stream. As described already, these will be made known (and available) in the form of calls to the receivedPacket() method of the PacketListener interface:


// Packet Listener interface:

 public void receivedPacket(PacketEvent pe) { Packet packet =
 pe.getPacket(); System.out.println("RECV:" + packet.toString());

The method receives a PacketEvent object, which represents both the event of a packet (an element) arriving and the packet itself, which we retrieve into packet with the getPacket() method. For debugging purposes, we print out what we get.

Now to determine what has actually arrived:


if (packet instanceof InfoQuery) { Enumeration e = ((InfoQuery)packet).Extensions(); if (e.hasMoreElements()) { Extension ext = (Extension)e.nextElement(); We check to see if the element is an IQ element, and, if so, we retrieve the extensions—the <query/> tags—that the element contains. Calling the Extensions() method on the packet object returns an Enumeration of those tags.

We're expecting only one tag, and we pull that into ext using the nextElement() method on our Enumeration.


{{Note|For the sake of simplicity, we're not checking here whether the <query/> tag received is qualified by the jabber:iq:rpc namespace. You might wish to do that in your version.

</code>

String request = ext.toString(); String id = ((InfoQuery)packet).getIdentifier(); JID from = ((InfoQuery)packet).getFromAddress(); We can pull the extension into string form, with the toString() method, ready for passing to our XmlRpcServer object. At this stage, ext contains the complete XML-RPC-encoded parcel, starting with the <methodCall> opening tag:


<methodCall> <methodName>examples.getCountyName</methodName> <params> <param> <value><i4>14</i4></value> </param> </params> </methodCall> So that we can send a response back to the requester, we need two other things from the incoming element besides the actual request payload. In general, when an <iq/> element is sent, representing a request, either as an IQ-get or an IQ-set, the sender is expecting the response, either an IQ-result or an IQ-error, with the same value in the id attribute. This is so the responses, once received, can be matched up with the original requests. So we need to store the id value, available to us through the getIdentifier() method of the packet object. We also need the JID of the sender, retrieved with the getFromAddress() method, so we can specify it in the to attribute of the IQ-result we'll be sending back.

Now it's time to service the request:


ByteArrayInputStream bis = new ByteArrayInputStream(request.getBytes()); String result = new String(responder.execute(bis)); With the execute() method of our XmlRpcServer object in responder, we convert the <methodCall/> into a <methodResponse/>, in effect. The decoding of the XML-RPC-encoded request, the determination of which method in which class to call (in our case it will be the getCountyName() method in our RPCHandler class), the calling of that method, and the encoding of the result into an XML-RPC-encoded response are all done magically and transparently for us by XmlRpcServer (thank goodness, or this recipe would be unbearably long!).

There's a bit of jiggling about required before we can make the execute() call, though. The method is expecting an InputStream object, and we've got a String. Not to worry, we just convert it with a ByteArrayInputStream, which can take a byte array (from request.getBytes()) and produce an InputStream object bis. Similarly, execute() produces a byte array, so that is converted to a String by wrapping the call with String().

The incoming XML-RPC-encoded request, being carried in the IQ element, will not include an XML declaration. Luckily, the XmlRpcServer does not mind that one is not present before decoding and dispatching the request. It will, however, include one on the encoded response it emits. So we must check for that and strip it off if there is one:


String response = result; int pos = result.lastIndexOf("?>"); if (pos >= 0) { response = result.substring(pos + 2);

       }

Now we have everything we need to return the XML-RPC-encoded response to the requester. It's time to call on the services of our IQRPC classes:


IQRPCBuilder iqrpcb = new IQRPCBuilder(); iqrpcb.setPayload(response); We create an instance of the IQRPCBuilder and call the single method setPayload() to load the XML-RPC-encoded response into the <query/> tag, which is qualified by the jabber:iq:rpc namespace. This is effectively the "extension" in JabberBeans parlance:


InfoQueryBuilder iqb = new InfoQueryBuilder();

       iqb.setType("result"); iqb.setIdentifier(id);
       iqb.setToAddress(from); iqb.addExtension(iqrpcb.build());
       InfoQuery iq; try { iq = (InfoQuery)iqb.build();
       }
       catch (InstantiationException ie) { System.out.println("Build
       failed: " + ie.toString()); return;
       }
       cb.send(iq);
     }
   }
 }

After creating an IQ element container using an InfoQueryBuilder and setting the appropriate attributes, we generate the <query/> tag (iqrpcb.build()) and add it to the IQ element container with addExtension().

The <iq/> is then generated—it now contains our payload—and sent back to the server, where it will be routed to the original requester.

That's pretty much all there is to it. We have a couple of other methods belonging to the PacketListener interface:


public void sentPacket(PacketEvent pe) { Packet packet = pe.getPacket(); System.out.println("SEND:" + packet.toString());

 }
 public void sendFailed(PacketEvent pe) { Packet packet =
 pe.getPacket(); System.out.println("Failed to send:" +
 packet.toString());
 }

} We fill these methods with debugging-style output statements for our convenience.



JabberRPCRequester


Now that we've got our Jabber-RPC responder all set up, it's time to turn our attention to our requester, JabberRPCRequester, shown in Example 10-12. This is a Python script that uses the Jabberpy library and Pythonware's xmlrpclib library. It's a pretty simple affair.


The JabberRPCRequester script, written in Python

import jabber import xmlrpclib import sys

Server = 'qmacro.dyndns.org' Username = 'client' Password = 'pass' Resource = 'jrpc-client' Endpoint = 'server@gnu.mine.nu/jrpc-server'; Method = 'examples.getCountyName';

county = int(sys.argv[1])

con = jabber.Client(host=Server) try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)

con.auth(Username,Password,Resource)

request = xmlrpclib.dumps(((county),), methodname=Method);

iq = jabber.Iq(to=Endpoint, type="set') iq.setQuery('jabber:iq:rpc') iq.setQueryPayload(request)

result = con.SendAndWaitForResponse(iq)

if result.getType() == 'result': response = str(result.getQueryPayload()) parms, func = xmlrpclib.loads(response) print parms[0] else: print "Could not complete call"

con.disconnect()


Looking at JabberRPCRequester Step by Step


After importing the libraries that we will need:


import jabber import xmlrpclib import sys we specify a number of parameters:


Server = 'qmacro.dyndns.org' Username = 'client' Password = 'pass' Resource = 'jrpc-client'

Endpoint = 'server@gnu.mine.nu/jrpc-server'; Method = 'examples.getCountyName'; The script connects to the Jabber server defined in Server, with the username defined in Username. The resource that will be passed in the authentication request is jrpc-client. There is as much significance in this name as there is in the name of the resource used by JabberRPCResponder (jrpc-server): none. It's just a useful naming convention to adopt when writing requesters and responders.

A single parameter, which will be interpreted as the index of the county to retrieve via the call to examples.getCountyName, is expected.


county = int(sys.argv[1]) The method expects an integer, so we convert it directly. This has a favorable secondary effect when we come to XML-RPC encode the request; if we hadn't called the int() function and left county as a string, this is what the XML-RPC-encoded parcel would have looked like:


<methodCall> <methodName>examples.getCountyName</methodName> <params> <param> <value><string>1</string></value> </param> </params> </methodCall> However, this is what we really want:


<methodCall> <methodName>examples.getCountyName</methodName> <params> <param> <value><int>1</int></value> </param> </params> </methodCall> In the same way as in the previous Python recipes, we connect to the Jabber server and authenticate:


con = jabber.Client(host=Server) try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)

con.auth(Username,Password,Resource) Now all we have to do is compose our XML-RPC-encoded request and send it on its way to the Jabber-RPC responder, which in our case, identified here by the JID in the Endpoint variable, is our JabberRPCResponder script.

It's time to call on the services of the XML-RPC library. We use the dumps() function to build an XML-RPC encoding, passing the single parameter (in a tuple, which is required by dumps()) representing the county index and the name of the method to call:


request = xmlrpclib.dumps(((county),), methodname=Method); We build the IQ-set containing a <query/> element in the jabber:iq:rpc namespace, by creating an instance of an Iq object, and calling methods to specify that namespace (setQuery()) and insert the XML-RPC encoding as the payload (setQueryPayload()):


iq = jabber.Iq(to=Endpoint, type='set') iq.setQuery('jabber:iq:rpc') iq.setQueryPayload(request) At this stage, we need to send it off and wait for a response. You have probably noticed that, unlike in previous Python recipes, this script hasn't defined or registered a callback to handle incoming elements. This is because we're approaching the task in a slightly different way in this script. The method used to send the element to the Jabber server—SendAndWaitForResponse():


result = con.SendAndWaitForResponse(iq) is the rather verbose cousin of the Jabber::Connection library's ask() method, as described in Section 10.1.5.3. It does "exactly what it says on the tin," namely, send the element off to the server and block until an element is received that is deemed, by a matchup of the id attribute values on both elements, to be the response. If no id attribute exists on the outgoing element, it is stamped with one (with a value unique within the current usage of the jabber class). Both are also relations of Net::Jabber's SendAndReceiveWithID().

So result receives an element object. It's an IQ element, as that is what a response to an IQ element will be. If the call transport (as opposed to the call itself) is successful, the response will be an IQ-result. If not, for example, if the Endpoint that we specified doesn't exist, then the response will be an IQ-error.


if result.getType() == 'result': response = str(result.getQueryPayload()) parms, func = xmlrpclib.loads(response) print parms[0] else: print "Could not complete call" Conversely, while the call transport might have succeeded, the call itself might have failed, for example, if we had specified a nonexistent method name in Method. However, for our purposes, we're going to assume that the call was successful. So we grab the payload (i.e., the contents of the <query/> tag) and string it with str().

The loads() function of xmlrpclib is used to extract the details from an XML-RPC-encoded parcel; it will produce two values, which we capture in resp and func. The first is the set of parameters, common to either a request or a response, and the second is the method name, if it exists in the encoding. We want to decode a <methodResponse/> here, so there's no method name present, but there should be a set of parameters.

We take the first (and only) element from the set of parameters in resp and print it out.


Jabber-RPC in Perl


Exciting as this recipe might be, it's not very visual. There are no screenshots of note to show, just a couple of STDOUTs from two scripts started at the command line. To fix this "problem," we're going to round this recipe off with a quick look at Jabber-RPC in Perl. Based on the Jabber::Connection library is a fairly new Perl library called Jabber::RPC, which sports two modules: Jabber::RPC::Client.pm and Jabber::RPC::Server.pm.

If you're slightly perplexed about what it takes to extend Jabber support in Java, take a look at Example 10-13 and Example 10-14. The first is an implementation, in Perl, of the JabberRPCResponder and all its class periphery. The second is an implementation of our JabberRPCRequester.


A Jabber-RPC Responder in Perl

use strict; use Jabber::RPC::Server;

my @county = ("Bedfordshire", "Berkshire", "Buckinghamshire", "Cambridgeshire", "Cheshire", "Cornwall", "Cumberland", "Derbyshire", "Devon", "Dorset", "Durham", "Essex", "Gloucestershire", "Hampshire", "Herefordshire", "Hertfordshire", "Humberside", "Huntingdonshire", "Kent", "Lancashire", "Leicestershire", "Lincolnshire", "London", "Middlesex", "Norfolk", "Northamptonshire", "Northumberland", "Nottinghamshire", "Oxfordshire", "Rutland", "Shropshire", "Somerset", "Staffordshire", "Suffolk", "Surrey", "Sussex", "Warwickshire", "Westmorland", "Wiltshire", "Worcestershire", "Yorkshire");

sub getCountyName { my $county = shift; return $county[$county - 1]; }

my $server = new Jabber::RPC::Server( server => 'gnu.mine.nu', identauth => 'server:pass', resource => 'jrpc-server', methods => {'examples.getCountyName' => \&getCountyName}, );

$server->start;

A Jabber-RPC Requester in Perl

use strict; use Jabber::RPC::Client;

my $client = new Jabber::RPC::Client( server => 'qmacro.dyndns.org', identauth => 'client:pass', resource => 'jrpc-client', endpoint => 'dj@localhost/jrpc-server', );

print $client->call('examples.getCountyName', $ARGV[0])

     || $client->lastFault;

That's it. There's no more code than that. So don't lose heart. Just use Perl; you know it makes sense.


Browsing LDAP


Browsing, a relatively new Jabber feature (introduced in Section 2.9 and described through its associated namespace in Section 6.2.5), is an extremely powerful and flexible beast. Whereas many of the standard Jabber namespaces such as jabber:iq:oob, jabber:iq:auth, and jabber:iq:register busy themselves with providing context for relatively narrow areas of information (out-of-band information exchange, authentication, and registration, respectively), jabber:iq:browse is a namespace that qualifies, and therefore defines, a flexible container that can be used to carry all sorts of information—information that is wrapped in meaning and context and that can have virtually unlimited levels of hierarchy. What's more, it can deliver information in a standard way that can be understood by Jabber clients.


{{Note|As of this writing, the only Jabber client to implement browsing is WinJab.

</code> Put another way, it means that we can extend the scope of Jabber deployment to areas outside what we traditionally see as the "Jabber world." The power and simplicity of the Jabber browsing namespace design means that we can adapt its use to whatever purpose we have in mind. As we push out the Jabber protocol and give entities within our networks the gift of speech and presence (in other words, give them a JID and graft on a Jabber connection stub), we will want to identify those entities as something more than a collection of hazy objects that sit behind Jabber addresses.

  • Want to find out about dictionaries that are reachable by Jabber?

Browse to a directory and pull out those JIDs—the dictionaries' addresses—that are identified as keyword/dictionary JID-types.

  • Want to allow your users to navigate an information hierarchy outside

the Jabber space but from within the comfort of their Jabber client? Build a "reflector" that navigates the hierarchy on behalf of your Jabber users and transforms it into a Jabber browsing context—by formulating the information in jabber:iq:browse terms. Jabber browsing is one of those oddities that is so simple and so ultimately flexible that it's sometimes better to demonstrate it than to talk about it. Let's have a look at what browsing can do by building ldapr, a "reflector" for information in a database accessed by the Lightweight Directory Access Protocol (LDAP).


Building the Reflector


We're going to build the reflector as a component that connects directly to the Jabber backbone. This makes sense, as it's a service that we'll probably want to run continuously (and perhaps start up and shut down in conjunction with the Jabber server itself), rather than something more transient like a client-based 'bot, for example.

Before we go any further, have a look at Figure 10-9.


Basic browsing in WinJab

</code>

This figure shows WinJab's browser window, which, when first opened, requests the top-level browse information from the Jabber server that WinJab is connected to (cicero, in this case). This is the information in the <browse/> section of the JSM component custom configuration, as described in Section 4.4.3.8, which looks like:


<browse> <conference type='public' jid='conf.cicero' name='Public Chat'/> </browse> We can see just one icon, representing the Public Chat conference service.

WinJab sensibly uses this location—the Jabber server (specifically the JSM) itself— as a starting position for browsing navigation. From the description of jabber:iq:browse in Section 6.2.5, we know that each element within a <browse/> section is identified with a JID, in the jid attribute. Here, the Public Chat element has a JID of conf.cicero. If we click on the element's icon in WinJab's browser window, it would make a further browse request—an IQ-get with an empty extension qualified by the jabber:iq:browse namespace—to that JID. Thus is a browse hierarchy descended. Example 10-15 shows what that browse request might look like.


Browsing to the Public Chat service

<iq type='get' to='conf.cicero' id='82A'> <query xmlns='jabber:iq:browse'/> </iq>


Identifying ldapr in the browsing hierarchy


This is exactly where our reflector service, in the form of a script called ldapr, will fit in. In the same way as we described our RSS news agent (newsagent) in the JSM configuration's <browse/> section, we'll now describe ldapr. We add it, like this:


<browse> <conference type='public' jid='conf.cicero' name='Public Chat'/> <service type='x-ldap' jid='ldap.cicero' name='LDAP Reflector'/> </browse> There are a couple of things to note in the definition:


service/x-ldap
The LDAP reflector is a service, so we use the service
category for the tag name used to describe it. While there are already
many subtypes defined within the jabber:iq:browse namespace
(such as irc, aim, and jud), none of them
matches what this service is going to offer, so in the same way as we
invent Multipurpose Internet Mail Extensions (MIME) subtypes in the
x- space, we specify x-ldap for the type
attribute here.
ldap.cicero
Each component has a JID. In browsing, the JID is the key to
navigation. When the icon representing this service is clicked, it's
to this JID that the browse request is sent. To provide a smooth
navigational path through the hierarchy, it's up to the component to
return browse data items that are identified by JIDs appropriate for
further navigation. We'll see what this means in the next section.


Navigating into the LDAP hierarchy


Having our reflector component defined in the JSM's <browse/> list makes for a smooth transition into the reflection. We're going to navigate the LDAP hierarchy in a similar way to what was described in Section 6.2.5.1. On receipt of an initial browse request, ldapr must return the top level of the LDAP hierarchy it has been set up to reflect:


<iq type='get' to='ldap.cicero' id='7'> <query xmlns='jabber:iq:browse'/> </iq> Figure 10-10 shows the LDAP hierarchy we've discussed in Section 6.2.5.1. It's part of an imaginary structure devised to represent people and departments in an organization. The base distinguished name, or base DN (an LDAP term meaning the common suffix used in the identifiers of all elements in a particular LDAP structure), is dc=demo,dc=org. A DN, or distinguished name, can be thought of as a key for a particular element. Levels within the LDAP structure are identified with DNs of ever-increasing lengths, as they get more specific the deeper the hierarchy is descended.


The LDAP hierarchy

</code> If

ldapr were to reflect this hierarchy, we want the response to an initial browse request to return information about the People and Groups nodes. In Example 10-16, we see what this response looks like.


An initial browse request elicits a reflection of the People and Groups nodes

SEND: <iq type="get" id="B88" to="ldap.cicero"> <query xmlns="jabber:iq:browse"/> </iq>

RECV: <iq id='B88' type='result' to='dj@cicero/basement' from='ldap.cicero'> <query xmlns='jabber:iq:browse'> <item jid='ldap.cicero/ou=People' name='ou=People'/> <item jid='ldap.cicero/ou=Groups' name='ou=Groups'/> </query> </iq> In the response, we're returning a single level of the LDAP hierarchy. The People and Groups nodes are represented by <item/> tags, within the generic container tag <query/>.

This is in slight contrast to the browse <iq/> elements shown in Section 6.2.5.1, in which, effectively, two levels of hierarchy are returned:


RECV: <iq type='result' id='B89' to='dj@cicero/basement' from='ldap.cicero'> <item name='root entry' xmlns='jabber:iq:browse' jid='ldap.cicero'> <item name='ou=People' jid='ou=People@ldap.cicero'/> <item name='ou=Groups' jid='ou=Groups@ldap.cicero'/> </item> </iq> It's really up to you to decide how many levels of information you want each browse response to emit. It depends on circumstances (will the requester be able to interpret multiple levels of hierarchy?) and the type of application scenario in which you're wanting to employ Jabber browsing. In this case, despite the difference in appearance, WinJab will interpret the information correctly whichever way you play it.


{{Sidebar|No Query Tag?This last example looks slightly odd, because the familiar <query/> tag, usually the container for information within various IQ namespaces, is conspicuously absent. It's actually still there in spirit, in the form of the first <item/> tag, which takes the <query/> tag's role in carrying the xmlns='jabber:iq:browse' namespace declaration.

</code> So, we've got our first two LDAP levels back. Figure 10-11 shows how they're displayed in WinJab's browser window. The JID we've just browsed to—ldap.cicero—is shown in the Jabber Address field. The People and Groups nodes are represented by <item/> tags within the jabber:iq:browse-qualified result; these are translated into folder icons in the browser window.


Showing the first hierarchy level in WinJab's browser

</code> The browser displays folder

icons for these nodes because we've described them using jabber:iq:browse's generic "holder" tag <item/>. While the namespace describes many categories to represent many different things (service, conference, user, and so on), there's not really a category that fits the "LDAP_ hierarchy node" description. So we plump for the generic <item/>, reserved specially for such occasions.

To navigate to the next level in the hierarchy, all we do is click on one of the icons now displayed to us. WinJab will send this next browse request to the JID that's associated with the icon we click. In the case of the People icon, we know from the browse result shown in Example 10-16 that the JID is ldap.cicero/ou=People. This is what that browse request and response look like:


SEND: <iq type="get" id="B105" to="ldap.cicero/ou=People"> <query xmlns="jabber:iq:browse"/> </iq>

RECV: <iq id='B105' type='result' to='dj@cicero/basement' from='ldap.cicero/ou=People'> <query xmlns='jabber:iq:browse'> <item jid='ldap.cicero/ou=UK, ou=People' name='ou=UK'/> <item jid='ldap.cicero/ou=France, ou=People' name='ou=France'/> <item jid='ldap.cicero/ou=Germany, ou=People' name='ou=Germany'/> </query> </iq> The results of this browse request are shown in Figure 10-12. Again, notice the JID displayed in the Jabber Address window—it's the JID that we've just browsed to. The JIDs that are assigned to the items displayed here reflect the next level in the hierarchy. And so it goes on.


Showing the first hierarchy level in WinJab's browser

</code>


What the reflector is actually doing


Each of the JIDs that are browsed to look like this:


[component name]/[relative LDAP DN] such as:


ldap.cicero/ou=UK, ou=People The hostname part of the JID—[component_name]—is the name of the LDAP reflector component, and the resource part of the JID—[relative_LDAP_DN]—reflects the DN of the node (minus the base DN suffix) within the LDAP structure that the <item/> with that JID represents.

The crucial bit is that the [component_name] part remains the same (ldap.cicero) across every call. This means that all of the jabber:iq:browse requests made in the navigation sequence go to the component, in this case, the ldapr script.

Once the component receives these requests, it disassembles the JID, extracting the resource part, and uses it to query the LDAP server on the Jabber client's behalf. It then builds a response, in the form of an IQ-result containing the all-important jabber:iq:browse-qualified extension, containing the results of the LDAP query.

JIDs are used to convey information to the component, as well as to have the request delivered to the right place.


{{Sidebar|Using JID Parts to Convey InformationThe example from Section 6.2.5.1 showed the information being conveyed as the user part of the JID:


<item name='ou=People' jid='ou=People@ldap.yak'/> This is perfectly fine, as long as the information we want to convey doesn't contain characters deigned illegal for that part of the JID. There are far fewer restrictions on how the resource part of a JID may be constructed. So it should usually be your first choice of location for piggybacking information:


<item jid='ldap.cicero/ou=People' name='ou=People'/> </code>


The ldapr script


While the explanation might be long, the actual script, shown in Example 10-17, is relatively short. Written in Perl, the ldapr script uses the Jabber::Connection library.


The ldapr script, written in Perl

use strict; use Jabber::Connection; use Jabber::NodeFactory; use Jabber::NS qw(:all); use Net::LDAP;

my $ldapsrv = 'cicero'; my $basedn = 'dc=demo,dc=org';

my $ldap = Net::LDAP->new($ldapsrv) or die $@;

debug("connecting to Jabber"); my $c = new Jabber::Connection( server => 'localhost:9389', localname => 'ldap.cicero', ns => 'jabber:component:accept', );

unless ($c->connect()) { die "Oops: ".$c->lastError; }

debug("registering IQ handlers"); $c->register_handler('iq',\&iq_browse); $c->register_handler('iq',\&iq_notimpl);

debug("authenticating"); $c->auth('pass');

debug("waiting for requests"); $c->start;


sub iq_browse {

 my $node = shift; return unless $node->attr('type') eq IQ_GET and
 my $query = $node->getTag(, NS_BROWSE);
 my ($obj) = $node->attr('to') =~ /\/(.*)$/; debug("request: $obj");
 my $result = $ldap->search( base   => $obj ? join(',', $obj,
 $basedn) : $basedn, filter => "(objectclass=*)", scope  =>
 'one', );
 if ($result->code) { debug("search error: ".$result->error);
 }
 else { foreach my $entry ($result->all_entries) { my $e =
 strip($entry->dn, $basedn); debug("found: $e"); my $item =
 $query->insertTag(isUser($e) ? "user" : "item");
 $item->attr('jid', join('/', $ID, $e)); $item->attr('name',
 [split(/,/, $e)]->[0]);
   }
 }
 $node = toFrom($node); $node->attr('type', IQ_RESULT);
 $c->send($node);
 return r_HANDLED;

}


sub iq_notimpl {

 my $node = shift; $node = toFrom($node); $node->attr('type',
 IQ_ERROR); my $error = $node->insertTag('error');
 $error->attr('code', '501'); $error->data('Not Implemented');
 $c->send($node); return r_HANDLED;

}


sub strip {

 my ($fqdn, $basedn) = @_; my @fqdn = split(/,/, $fqdn); my
 @basedn_elements = split(/,/, $basedn); return join(',', @fqdn[0 ..
 ($#fqdn - scalar @basedn_elements)]);

}


sub toFrom { my $node = shift; my $to = $node->attr('to'); $node->attr('to', $node->attr('from')); $node->attr('from', $to); return $node; }

sub isUser {

 my $rdn = shift; return $rdn =~ /^cn/ ? 1 : 0

}


sub debug {

 print STDERR "debug: ", @_, "\n";

}


Looking at ldapr Step by Step


Taking the script step by step, we start on familiar ground:


use strict; use Jabber::Connection; use Jabber::NodeFactory; use Jabber::NS qw(:all); use Net::LDAP;

my $ldapsrv = 'cicero'; my $basedn = 'dc=demo,dc=org'; After declaring the modules we want to use (the only one we haven't seen so far is the Net::LDAP module that we'll need to connect to and query an LDAP server), we define a couple of variables. $ldapsrv is the name of the LDAP server that ldapr is going to be reflecting, and $basedn is the base distinguished name that will be used as the suffix in all of the LDAP queries.

If you don't have an LDAP server of your own, a number of public ones are available that you could point this script at. The two variables $ldapsrv and $basedn go together—make sure you specify the correct base DN for the LDAP server you want to reflect.


{{Note|Depending on the configuration, some LDAP servers will require you to bind to them with a username and password before you can perform searches. To do this, you'll need to include an extra step in this script, using the bind() method in Net::LDAP.

</code> Having opened our connection to the LDAP server:


my $ldap = Net::LDAP->new($ldapsrv) or die $@; we then proceed to connect to the Jabber server as a component. We're connecting to localhost:9389, which means this component script is going to run on the same host as the Jabber server, and connect to it on port 9389:


debug("connecting to Jabber"); my $c = new Jabber::Connection( server => 'localhost:9389', localname => 'ldap.cicero', ns

 => 'jabber:component:accept', );

unless ($c->connect()) { die "cannot connect: ".$c->lastError; } We'll also need a <service/> stanza in the Jabber server's configuration file to describe this component. The stanza in Example 10-18 would be appropriate. Refer to Section 9.3.1.1 for more details on connecting to Jabber as a component.


A component instance definition for ldapr

<service id='ldap.cicero'> <accept> <ip>localhost</ip> <port>9389</port> <secret>secret</secret> </accept> </service> The script isn't going to do much apart from reflect jabber:iq:browse queries as LDAP searches. Consequently, it doesn't need to be able to handle anything apart from incoming IQ elements:


debug("registering IQ handlers"); $c->register_handler('iq',\&iq_browse); $c->register_handler('iq',\&iq_notimpl); The iq_browse() function is where all the work will be done. As in the newsagent recipe (Section 8.3)," we also have a "catchall" function (iq_notimpl()) that cleans up any "stray" IQ elements that it doesn't know about. Furthermore, because we aren't registering any handlers for <message/> or <presence/> elements, the dispatcher in Jabber::Connection will just throw them away, leaving ldapr blissfully ignorant of them—which is what we want.

After authenticating with the server by calling the auth() method to send the <handshake/> element:


debug("authenticating"); $c->auth('secret'); it's time to set the main event loop going. With a call to start(), we hand over control to Jabber::Connection, safe in the knowledge that we have a function (iq_browse()) to deal with the incoming requests that we're supposed to deal with, and that we don't care a hoot about anything else:


debug("waiting for requests"); $c->start;


Performing the actual reflection


Now, let's move on to the meat of the script. The main handler, iq_browse(), starts by making sure it has an IQ element:


sub iq_browse {

 my $node = shift; return unless $node->attr('type') eq IQ_GET and
 my $query = $node->getTag(, NS_BROWSE);

What we're looking for is an IQ-get with a jabber:iq:browse-qualified query extension. If there isn't one, we exit out of the function, and dispatching continues to the iq_notimpl() function, because we didn't return the special value represented by m_HANDLED.

If we do get a valid request, we first extract the relative DN from the resource part of the JID specified in the IQ-get's to attribute—the JID. If the request was sent to ldap.cicero/ou=UK, ou=People, then we extract the relative DN ou=UK, ou=People into $obj like this:


my ($obj) = $node->attr('to') =~ /\/(.*)$/; debug("request: $obj"); Armed with a specification of what part of the LDAP hierarchy needs to be searched, the next step is to call the search() method on the LDAP object in $ldap:


my $result = $ldap->search( base => $obj ? join(',', $obj, $basedn) : $basedn, filter => "(objectclass=*)", scope => 'one', ); As you can see, we're specifying three parameters in the search() method:


base
This is the point within the LDAP hierarchy from which to start
looking. We must specify this as a full DN, so we append the base DN
(dc=demo,dc=org) to the relative DN received in the request
to make an absolute DN:

ou=UK, ou=People, dc=demo, dc=org

filter
Specifying (objectclass=*) effectively means "look for
anything."
scope
There may be many levels below the point in the hierarchy that we're
going to start searching from. Specifying 1 for the scope
parameter tells the search to descend only one level. After all, we
want to return only one level back to the requester in the reflection.
If the search failed for some reason (e.g., if a relative DN specified
in the request didn't exist in the hierarchy),[2] then don't bother checking the
results. Instead, a warning debug message with the error details is
issued:


if ($result->code) { debug("search error: ".$result->error);

 }

However, if the search succeeded, the results should be translated into the jabber:iq:browse extension:


else { foreach my $entry ($result->all_entries) { my $e = strip($entry->dn, $basedn); debug("found: $e"); my $item = $query->insertTag(isUser($e) ? "user" : "item"); $item->attr('jid', join('/', $ID, $e)); $item->attr('name', [split(/,/, $e)]->[0]);

   }
 }

Calling the all_entries() method on the search results returns a list of LDAP entries, in the form of objects. Calling the dn() method on one of these objects (in $entry) returns us its full DN. Searching with a base of:


ou=UK, ou=People, dc=demo, dc=org in the demonstration database would return two entries:


cn=Janet Abrams, ou=UK, ou=People, dc=demo, dc=org cn=Paul Anthill, ou=UK, ou=People, dc=demo, dc=org The task of this section of the iq_browse() function is to turn the information, in the form of these two entries, into something like this:


<iq id='B25' type='result' to='dj@cicero/basement' from='ldap.cicero/ou=UK, ou=People'> <query xmlns='jabber:iq:browse'> <user name='cn=Janet Abrams' jid='ldap.cicero/cn=Janet Abrams, ou=UK, ou=People'/> <user name='cn=Paul Anthill' jid='ldap.cicero/cn=Paul Anthill, ou=UK, ou=People'/> </query> </iq> which in turn should be rendered into something like the contents of WinJab's browser window as shown in Figure 10-13.


Showing the third level of hierarchy in WinJab's browser window

</code> The function strip()

takes two arguments and strips away a base DN from a fully qualified DN to leave the significant, relative part. Calling strip() on this:


cn=Janet Abrams, ou=UK, ou=People, dc=demo, dc=org when specifying the base DN in $basedn, would return this:


cn=Janet Abrams, ou=UK, ou=People For each of the entries found, we insert a tag into the <query/> extension. The name of the tag is either item, if the entry is simply an LDAP hierarchy node, or user (a valid jabber:iq:browse category), if the entry points to a person. We make the decision on the basis of the first part of the DN—if it's cn=, then it's a reference to a person. The isUser() function makes this decision for us.

Once inserted, we embellish the <item/> or <user/> tag with name and jid attributes. The jid attribute is crucial, as it represents the path to further descend within the LDAP hierarchy on the next request. It is given the whole of the relative DN as a value. The name attribute is simply given the most significant portion of the DN as a value.

Whether we found something or not, we still want to return a result to the requester:


$node = toFrom($node); $node->attr('type', IQ_RESULT);

 $c->send($node);

So we swap around the to and from attributes (remembering we're a component, and not a client) change the IQ element's type from get to result, and send it back.

We end the function by telling the dispatcher that the element has been handled:


return r_HANDLED;

}


Supporting functions


The rest of the script consists of minor functions that play roles in assisting the core iq_browse().

The iq_notimpl() function is exactly the same as in the newsagent script in Section 8.3; it serves to catch stray IQ elements that in this case aren't IQ-gets containing a jabber:iq:browse query, and send them back with a "Not Implemented" error. With IQs, this is preferable to not responding at all, as responses are usually expected, even if those responses are IQ-errors. It's different with <message/> and <presence/> elements, as they can be seen as "one-way" and valid fodder for an element-sink.


sub iq_notimpl {

 my $node = shift; $node = toFrom($node); $node->attr('type',
 IQ_ERROR); my $error = $node->insertTag('error');
 $error->attr('code', '501'); $error->data('Not Implemented');
 $c->send($node); return r_HANDLED;

} The strip() function, as described already, removes a base DN from a fully qualified DN:


sub strip {

 my ($fqdn, $basedn) = @_; my @fqdn = split(/,/, $fqdn); my
 @basedn_elements = split(/,/, $basedn); return join(',', @fqdn[0 ..
 ($#fqdn - scalar @basedn_elements)]);

} As well as sharing the iq_notimpl() function with the newsagent script, ldapr also shares the toFrom() function, which flips around the values of the to and from attributes of an element:


sub toFrom { my $node = shift; my $to = $node->attr('to'); $node->attr('to', $node->attr('from')); $node->attr('from', $to); return $node; } The isUser() function, also described earlier, makes an arbitrary distinction between LDAP nodes that it thinks are People and those that it thinks aren't:


sub isUser {

 my $rdn = shift; return $rdn =~ /^cn/ ? 1 : 0

} Last but not least, we have a simple debug() function, which in this case is simply an abstraction of the classic "print to STDERR" method:


sub debug {

 print STDERR "debug: ", @_, "\n";

}


Building an ERP Connection


To some extent, SAP's R/3, an Enterprise Resource Planning (ERP) system, has until recently been a monolithic piece of software, both from an actual and psychological perspective.

The drive to replace disparate and incompatible business system "islands" with an integrated software solution that covered business processes across the board was strong in the 1980s and 1990s. With good reason. For example, SAP delivered the ability for companies to manage their different end-to-end processes coherently, without requiring custom-built interfaces between logistics and financial accounting packages. The flow of information was automatic between the application subsystems that were standard within SAP's R/2 and R/3 products.

The upside to this was, of course, the seamless integration of data and functions within the SAP universe, a universe vast enough to offer application coverage for pretty much every conceivable business function.

The downside was, ironically, the universe itself. All-encompassing as SAP's products were, and indeed they were forever changing and expanding to meet business demands, it was never enough, if you wanted to break out of the mould formed by the omnipresent standard proprietary client called SAPGUI. Sure, these days, with a huge installed base of R/2 and R/3 systems in production around the world, there are a multitude of ways to get data in and out of SAP systems, but despite the varied successes of alternative client initiatives, user interaction with the business processes remains largely orientated around the SAPGUI.

This recipe is extremely simple compared to the others in this chapter. It breaks out of the SAPGUI mold and the monolithic software culture to use open source tools and technologies to add value to our SAP business processes. The point of this recipe is not particularly what the script looks like or how it's written, but what it does and how it does it.



Building an Order Approval Notification Mechanism


We're going to use a standard Jabber client as an SAP R/3 client—obviously not to replace SAPGUI, rather to allow someone who perhaps has a single R/3-related task to perform and connects only to SAP occasionally. In stark contrast to the SAPGUI client, an off-the-shelf Jabber client is much smaller. It takes up less screen space, memory, and CPU and generally for focused access is a great way for someone to play his part in business processes from the comfort of familiar communication surroundings—his IM client.

Of course, the available Jabber clients don't have any built-in R/3 functionality per se, but as clients that can receive messages and recognize URLs,[3] they provide enough horsepower for us to achieve our goal.

That goal is to notify a supervisor whenever a sales order is placed that requires his approval. The notification will arrive in the form of a <message/> element, carrying some descriptive text and, crucially, a URL, which points to an Apache-based handler. When invoked, the handler pulls the relevant information for the order out of R/3, requests verification of the viewer's identity, and offers a chance to approve the order with the click of a button.

Effectively we're building a miniworkflow scenario: in one direction a notification is transmitted out of the bounds of the SAP universe to the approver, and in the other direction the notification process is turned around in a one-step approval cycle via Apache. Figure 10-14 shows this scenario and where our Jabber client and script, called approv, fits in.


An external order approval workflow

</code> The goal here is to get the

notification message out of R/3 and send it to the supervisor's JID along with a URL that he can follow back into the R/3 system to carry out the approval process. The return process via Apache is outside this recipe's scope and has been left as an exercise for you.



Getting the notification out of R/3


As we've already mentioned, there are many ways of getting data in and out of SAP. We're going to use a generic, lowest common denominator feature of the R/3 Basis system to invoke a script and pass it parameters.


{{Note|At this stage, if you're squeamish about R/3 Basis or SAP's ABAP language or are of another ERP persuasion, it's time to look away. What we're going to do here is not rocket science, nor is the general process specific to R/3. We're just going to call a script, at the operating system level, from within an application inside R/3.

</code> The function group SXPT encompasses a number of function modules related to the definition, management, and execution of operating system commands. Each of these commands is described within sets of configuration parameters that define how and where they can be invoked. Using the program RSLOGCOM, you can create definitions for these operating system commands manually.

We need to define such a command that refers to the approv script. Figure 10-15 shows the RSLOGCOM definition of approv as an external command that is called ZNOTIFY.


Defining approv as an external command with RSLOGCM

</code> Once approv has been defined

this way, it can be invoked by passing parameters with a call to a function module in the SXPT function group (SXPG_EXECUTE_COMMAND). Example 10-19 shows how the script might be invoked using this function module in ABAP. Code like this could typically be installed in a customer exit—a place in a standard R/3 application where custom processing can be added without having to go through the involved process of creating and carrying out a modification request.


Calling approv, via ZNOTIFY, from within R/3

data: sxpg_exec_protocol like btcxpm occurs 0 with header line,

     sxpg_add_parms like SXPGCOLIST-PARAMETERS,
     sxpg_target_system like RFCDISPLAY-RFCHOST value 'gnu.mine.nu'.

concatenate ordernumber approver into sxpg_add_parms separated by space.

call function 'SXPG_COMMAND_EXECUTE' exporting commandname = 'ZNOTIFY' additional_parameters = sxpg_add_parms operatingsystem = sy-opsys targetsystem = sxpg_target_system stdout

= 'X' stderr                = 'X' terminationwait       = 'X' table

exec_protocol = sxpg_exec_protocol exception others

 = 1.

if sy-subrc <> 0. raise notification_failed. endif. We pass two parameters to approv via the SXPG_COMMAND_EXECUTE: the number of the order that needs approving (from ordernumber) and the JID of the approver (from approver). The SXPG mechanism invokes the approv script with the two parameters, and the notification starts its journey.


The approv Script


As mentioned already, the approv script (shown in Example 10-20) is rather small and insignificant. Its purpose is to take the order number it receives, wrap it in a descriptive message that includes a URL, and send it to the approver's JID. Written in Perl, the approv script uses a minimum set of features from the Jabber::Connection library.


The approv script, written in Perl

use strict; use Jabber::Connection;

my ($order, $approver) = @ARGV; my $c = new Jabber::Connection(server => 'qmacro.dyndns.org'); die "Cannot connect: ".$c->lastError unless $c->connect(); $c->auth('approv','secret','approv');

$c->send(<<EO_MSG); <message to="$jid"> <subject>Order Approval Required</subject> <body> An order ($order) requiring your approval has been placed. Please visit http://qmacro.dyndns.org/approv?$order to approve.

Thank you. </body> </message> EO_MSG

$c->disconnect; The script receives the two parameters passed from R/3 into the $order and $approver variables. Having connected to the Jabber server at qmacro.dyndns.org and authenticated as the user approv, it sends off a formatted message to the approver, before disconnecting.

The script itself is extremely simple and is not of primary importance. What is crucial here is the role that Jabber is playing. The <message/> element of Jabber's protocol is used to span the R/3 world with the IM world, enabling a business process cycle to take place outside the normal boundaries of R/3 interaction. On a simple level, that's all it takes to merge the ERP business process world with the world of IM.

The return path to R/3, via the Apache-based CGI application, is initiated when the user clicks on the URL in the message, as shown in Figure 10-16.


Receipt of an order approval request in Jarl

</code> Here we see that Jarl has

recognized the http:// address and has rendered it as an active link. That's all it has to do. There's no attempt on Jarl's part to make an HTTP request to the Apache server and render the HTML received. Focusing on "the right tools for the right job," we should leave that role to a web browser, as indeed Jarl does, starting up the browser of our choice.[4]


Taking This Further


The journey through the recipes in the last three chapters of the book has taken us from the simplest CVS notification mechanism with the cvsmsg script (Section 8.1) through a fairly involved RSS component (Section 9.3) to the transporting of XML-RPC-encoded requests and responses with our JabberRPCRequester and JabberRPCResponder scripts (Section 10.3).

This chapter, and indeed the book, ends on a simple note, in the form of the approv script. Not without reason, we've completed the circle of script and application complexity. While we can build very useful and successful applications that are naturally complex, a Jabber-powered solution doesn't necessarily have to be. To employ the Jabber philosophy, the technology, and the protocol elements to build bridges between previously separate systems, and to span different areas of technology, open and proprietary alike, using Jabber's open, extensible, and flexible protocol, is what it's all about.

Finally, it's hopefully clear from the diversity of recipes shown in this part of the book that deploying solutions with Jabber, as noted in the preface, really is fun!

Personal tools