Home > Articles > Web Services > XML

This chapter is from the book

This chapter is from the book

The <iq> Element

The login example showed a number of uses for the <iq> tag, and it acted as a container for both outgoing requests (from the client to the server) and incoming responses (server to client). Note that in contrast to certain other protocol structures, the info/query streams are "in band" with other message types and can occur asynchronously. A nice side effect of completely in-band protocols is that unlike email, where registration is a completely separate and distinct process, a new user can register with a reachable Jabber server on the fly.

Other than the asynchronous nature of requests and responses, the mechanism is like HTTP-GET or -PUT: It enables two entities to make requests and receive responses.

An info/query stanza may possess the following attributes, though not all are required or even appropriate in every query conversation, and might reasonably be ignored by either participant in the conversation:

  • to—Specifies the intended recipient of the query stanza. This is optional for most query types, as for example when we asked for the registration information from the server earlier or even when we sent back our registration information. In the first case, the jerry-rigged "client" just wanted some information from the physical jabberd to which we Telneted. It would have been superfluous to send a specific to entity. In both cases, the client had a really good idea of the identity of the intended recipient. Equally, when the Jabber server responded, it had a good idea of the physical entity to whom it was responding (and no idea of a jabber-id because one didn't yet exist). It might be, however, that the info/query is not destined for the jabberd itself but rather for the account setup portion of your news and sports delivery component, in which case the switch will definitely want a destination address to which the query stanza should be routed.

  • from—Specifies the sender of the query stanza. This is optional for most query types, as for example when we asked for the registration information from the server earlier, or even when we sent back the registration information. Because the user didn't exist at either of those points in time, it would have been strange (actually incorrect) for the server to expect a jabber-id. The same holds true of the server's responses. Again, it might be the case that the info/query is not destined for the jabberd itself, in which case the sender's JID might be important. The from='' attribute is checked by the server to see whether it matches your user@server. If that part does not match, then it overwrites the from='' you sent from the Client with the JID that you used to authenticate (that is log in). This enables you to control your resource, but you cannot SPAM other users by saying that you are someone else.

  • id—An optional unique identifier for the purpose of tracking the request-response interaction. The sender of the query stanza could set this attribute, which may be returned in any replies. As you may have noticed in the previous examples, you could type in any valid string for an ID. The responder used the same ID to let the sender know to what the response referred. Writing a real client, you would probably want to use a random number generator to assure uniqueness for query ID.

  • type—This attribute is required in all queries and specifies a distinct point of interaction. Transactions and responses generally follow the pattern shown in Figure 3.6.

Figure 3.6Figure 3.6 Common C2S queries and responses.

The type Attribute

If you look back at the example client-to-server (C2S) interactions, you will have noticed that the dialogue involved in a query is actually pretty simple, and is defined by the type attribute in the <iq> tag. You can either "get" something (information usually) from a Jabber server, or "set" something at the switch. Normally, a "set" sends stuff to the switch, which will persist for you in the .xml data file it stores on your behalf. What you get back is either a "result" or an "error." Any query-capable client you write therefore simply has to know how to generate queries and tell the difference between good and bad results.

You saw an example of a "get"-type query in the request for registration information earlier, and a "set"-type query when registration information was supplied to create the user. You saw "result" and "error" responses when we successfully registered a new user and then tried to register again.

The value of type must be one of the following (anything else is ignored):

  • get—The stanza is a request for information. The details of the request will be found in the child element for the tag. The child element must be qualified by namespace. For example, a request for registration information uses the xml namespace jabber:iq:register. A login request uses jabber:iq:auth. The get queries shown in the C2S example in Figure 3.6 expect to see one of the results returned.

  • set—The stanza contains data intended to provide required data (most likely specified in a previous returned result, set new values, or replace existing values. The 'get' queries shown in the C2S example in Figure 3.6 expect to see one of the results returned.

  • result—The stanza is a response to a successful get or set request. The result might have a single XML child element with enumeration and/or clarification of data required in a subsequent set query. The single XML child element is a <query> tag pair. In the example that requested registration information, the server returned a <query> with several children, with each tag representing a required bit of information or a clarification (the <instructions> element) suitable for display out to a human reader:
  • <query xmlns='jabber:iq:register'>
     <password/>
     <instructions>
    Choose a username and password to register with this server.
     </instructions>
     <name/>
     <email/>
     <username/>
    </query>
  • error—Means that an error has occurred in the processing (or delivery, in the case of a message being routed to some other entity) of a previously-sent get or set. The returned stanza must include a single <error/> child element, which in turn must contain a numeric code attribute corresponding to one of the standard error codes. It could also contain an optional text string as element text corresponding to a description of the error. In the example where we tried to double register "Mr. Praline," the server returned an <iq> whose type was error and whose child element was <error code='409'>Username Not Available</error>. A complete list of error codes is shown in Table 3.1.

Table 3.1 Complete List of Jabber Error Codes

Code

Description

302

Redirect

400

Bad Request

401

Unauthorized

402

Payment Required

403

Forbidden

404

Not Found

405

Not Allowed

406

Not Acceptable

407

Registration Required

408

Request Timeout

409

Conflict

500

Internal Server Error

501

Not Implemented

502

Remote Server Error

503

Service Unavailable

504

Remote Server Timeout

510

Disconnected


TIP

If you're familiar with HTTP, you'll recognize the numbers in Table 3.1 as the same as HTTP status codes.

Data and Namespaces in <iq> Elements

A key point is that the data content of both requests and responses is qualified by the namespace declaration of a single direct child element of the <iq> element. That is, to get a definition of what the data mean, you can use the namespace to clarify. The types of topics that can be included in an <iq> query are qualified by different namespaces that can be included in the tag. When we logged in with the following:

<iq id='WhateverYouWantHere' type='set'>
 <query xmlns='jabber:iq:auth'>
 <username>Praline</username>
 <password>Cleese</password>
 <resource>telnet</resource>
 </query>
</iq>

the meaning of the children tags of the <query> tag are explained in the jabber:iq:auth namespace.

If you are an XML purist we should say that an <iq> element can have only one legitimate child, the <error/> tag. Otherwise however, whenever there's a <query> element, the children are always XML from another namespace. An info/query stanza can contain any properly-namespaced child element. Even a simple result might contain such, although it might be unusual. Implement only what is truly necessary in your client and forget the rest. A complete list of all the namespaces relevant in Jabber, including those used in info/queries, is shown in Table 3.2.

Table 3.2 Complete List of Jabber Namespaces

Mnemonic (from jabberPy library)

Namespace

NS_CLIENT

jabber:client

NS_SERVER

jabber:server

NS_AUTH

jabber:iq:auth

NS_REGISTER

jabber:iq:register

NS_ROSTER

jabber:iq:roster

NS_OFFLINE

jabber:x:offline

NS_AGENT

jabber:iq:agent

NS_AGENTS

jabber:iq:agents

NS_DELAY

jabber:x:delay

NS_VERSION

jabber:iq:version

NS_TIME

jabber:iq:time

NS_VCARD

vcard-temp

NS_PRIVATE

jabber:iq:private

NS_SEARCH

jabber:iq:search

NS_OOB

jabber:iq:oob

NS_XOOB

jabber:x:oob

NS_ADMIN

jabber:iq:admin

NS_FILTER

jabber:iq:filter

NS_AUTH_0K

jabber:iq:auth:0k

NS_BROWSE

jabber:iq:browse

NS_EVENT

jabber:x:event

NS_CONFERENCE

jabber:iq:conference

NS_SIGNED

jabber:x:signed

NS_ENCRYPTED

jabber:x:encrypted

NS_GATEWAY

jabber:iq:gateway

NS_LAST

jabber:iq:last

NS_ENVELOPE

jabber:x:envelope

NS_EXPIRE

jabber:x:expire

NS_XHTML

http://www.w3.org/1999/xhtml

NS_XDBGINSERT

jabber:xdb:ginsert

NS_XDBNSLIST

jabber:xdb:nslist


Jabber Presence

The <presence/> element has a number of attributes and subelements which we'll present in practical usage first, then document more formally.

We presented an instance of this tag earlier in the Telnet session when we suggested you try typing in a single-line stanza consisting of only a <presence/> element. Remember that the Jabber server returned a number of <presence> elements in response. The primary purpose of this element is the synchronization of presence and availability among peers who care. By definition, and in every switch implementation, this means each member of a client's roster. Normally, the switch may cache this list, but because the in-memory list is a clone copy, it may refer to the persistent stored version for reliability as needed.

Notice that you didn't need to add to or from' attributes to your initial presence message. The server's automatic reaction upon receiving an otherwise unqualified <presence/> message is to read your roster from its external persistent store and do two things. First, it gets the presence of the members of your roster list by sending them presence messages with an attribute of probe. For example:

<presence type='probe' to='jane@localhost'
 from='praline@localhost'/>

If the server can't connect with the probed user, then it simply drops the packet. Because virtually every GUI client initializes with client's entire roster shown as "offline," the portrayal of roster items changes only on arrival of an appropriate <presence/> message.

Second, if the server does connect to a member of your roster, the delivery of a presence-probe message to the client stimulates the client to reply with its complete presence state. What gets returned to you may vary from client to client. The server delivers that message back to your client, which then updates its GUI. You also did not have to specify a status element on your initial presence message. The server assumes a state of "available" for you.

This "whatever happens, happens" brand of interaction is illustrated in Figure 3.7. The server receives a simple <presence/> message from Client A, reads Client A's roster and expands, then sends presences messages to Client B and C. B responds with a <presence/> packet addressed to Client A, which then updates its GUI, but no response is ever received from Client C. Of course, when Client C finally does come online, it will send a <presence/> packet to Client A, to which Client A will respond; on response, Client C updates its GUI.

Figure 3.7Figure 3.7 Presence probes and response interactions.

You've seen how the server mediates on behalf of Client A to multicast its presence to its roster. So how does Client A get updated with Client C's presence when Client C comes online, or from Client B when its presence changes? The user at Client B might, for example, explicitly set a presence using the client GUI, or Client B might just disconnect.

In both cases, the server actually does the honors, as one client doesn't normally send presence packets directly to another (although it's possible to bypass the server's Jabber session management—JSM—module if both parties are subscribed to each other's presence). Because Praline@localhost and jane@localhost are subscribed to one another, Mr. Praline can, after login, simply generate a packet like this:

<presence to='jane@localhost/Home' from='Praline@localhost'>
 <status>returning dead parrot</status>
 <priority>0</priority>
</presence>

and toss it over the wall to the server. If you have debug turned on at the server (for example, by running the server from the command line with the –D option), as in

.\jabberd -D -H .

you'll first see the routing request being generated by the server, then the following:

deliver.c:257 deliver(
to[jane@localhost/Home],from[Praline@localhost],type[2],packet[
 <presence to='jane@localhost/Home' from='Praline@localhost'>
 <status> returning dead parrot </status>
 <priority>0</priority>
 </presence>
])

The server is generally quite happy to route something for you, so it wraps the request in a <route> packet and delivers it to jane@localhost. The problem is (as you'll notice if you run this experiment) that jane@localhost does not respond with its presence. You are short-circuiting the presence and availability mechanism, so this is not a wise way to write a client. Rather you should rely upon the subscription mechanisms in the Jabber server to make the necessary circuits for you.

To allow the server to manage subscriptions for a client pair, the two clients must mutually agree to subscribe to each other's presence and availability. One client can't just probe another client to whom it's not subscribed. Try it using the Telnet approach you saw earlier to generate a presence probe, in this case from Mr. Praline to bill@localhost (these two are not cross-subscribed):

<presence to='bill@localhost/Home' from='Praline@localhost'
 type='probe'/>

The server responds by dropping the packet, as shown in the following server trace:

Sun Feb 23 14:39:59 2003 mod_presence Praline@localhost
attempted to probe by someone not qualified

You can, however, probe someone with whom you have agreed to share presence information. Here, praline@localhost and jane@localhost have both agreed to share presence, so Mr. Praline can type in (in a Telnet session):

<presence to='jane@localhost' from='Praline@localhost'
 type='probe'/>

And now the server allows the connection. In response, jane@localhost replies:

<presence from='jane@localhost/Home' to='Praline@localhost'>
 <status>available</status>
 <priority>0</priority>
 <x xmlns='jabber:x:delay' from='jane@localhost/Home' 
stamp='20030223T19:42:13'/>
</presence>

There are some interesting subelements in the response packet. The <status>... bit you have already seen. Generally, clients return either a <show></show> pair or a <status></status> pair (but not often both). Either contains CDATA, which can be any free text, as we document later. There's also a <priority> tag, which is used to help distinguish one presence from another: It may be the case that you are logged in to a particular Jabber server on two different machines; a higher priority value in the element suggests to the server that this is the client to preferentially route packets. Finally, the <x xmlns='jabber:x:delay'...> bit is a time stamp that a client can use to determine the vintage of information. In this example, the available status was good as of about 7:42 p.m. (time-relative to the server).

Notice that the entire series of conversations takes place asynchronously and relies on each party in the conversation to do its part. What impresses us as especially clever and in keeping with the overall design philosophy is the asynchronous nature of so many things in Jabber. There is very little need for the Jabber server to keep huge amounts of state information for clients. The switch simply attempts to act as a switch or a router. If it's unable to route a message, then it allows the failure to happen. When clients are offline, their information is persisted into an XML file, including their roster contents, and (optionally) their preferences. When a client returns online, the Jabber server reads in the persisted XML file, whose format we covered in Chapter 2, "Installing and Configuring Jabber Software," using its XDB capability, and acts upon what it finds (particularly roster information) there. The switch follows to the rule, "Do only what's required and no more." Strict adherence to this rule makes for a lean and mean switch, both functionally and footprint-wise.

Notice too the division of labor among parties in Jabber. The server merely delivers the presence-probe to the clients on your roster. What they choose to do in response to the probe is not the server's concern. Clients most often respond by sending a presence packet—indeed that's the expected behavior. When a client sends a responsorial presence packet, the server simply delivers it to you without having to remember that this packet is somehow connected with something that it sent to you previously.

NOTE

We aren't going to cover the intricacies of writing a complete server in this book, but understanding these concepts will help you should you decide to use the open source Jabber server as a way of getting started toward building your own or making your own contribution to the open source implementation.

Presence Attributes

In the primitive example at the beginning of the chapter, the presence message was unmodified by additional attributes, but there are a few worthy of mention that you need to put into your programming toolkit. Let's formally talk about them now.

The <presence> Element

In addition to being used as a carrier for clients' presence and availability, the <presence/> element is also used by the server as a management tool for connected clients. The <presence/> element is also used to negotiate and manage subscriptions to the presence of other entities. You have seen some practical examples of using presence already, but to formalize your understanding, you should look at some of the attributes of the <presence/> tag itself.

The <presence/> Attributes

A presence element can have several attributes, not all of which are appropriate to a given purpose.

  • to—Specifies the intended recipient of the presence stanza (if any). Often, this attribute is filled in by the Jabber server rather than specified by a client. A <presence/> message with no other qualification attributes is normally issued when a client session connects. This is expanded by the Jabber server into messages for each of the client's roster members. After it is expanded by the server, a <presence> message contains both to and from attributes. Examples were shown earlier in the section "Protocol Mechanics."

  • from—Specifies the sender of the presence stanza. Normally this is reflected back to a sender as a part of a response to a presence multi-cast managed by the Jabber server's session manager module. Again, the from attribute is verified against your JID and overwritten if the user@server does not match the JID with which you logged in. Usually it's safest to not send it.

  • id—A unique identifier for the purpose of tracking presence. The sender of the presence stanza sets this attribute, which may be returned in any replies. Although it is permissible to include an id attribute, it has no value for presence management.

  • type—Describes the availability state, subscription request, presence request, or error. When there is no type attribute there is an implied value of available. When specified, type should have one of the values shown in Table 3.3.

Table 3.3 Complete List of Jabber <presence/> Type Attributes

Value

Significance

available

This is the default availability value. To signify a presence of "available," don't set a type attribute at all in the outgoing packet.

unavailable

The sending client (identified in the from attribute) is no longer available for communication. This likely will have been sent by the Jabber server (on a client's behalf) when a client disconnect was detected.

probe

This attribute is normally issued by the Jabber server on behalf of a client and addressed to the clients on the originating client's roster. The normal form is:

<presence type='probe' to='user@server' from='user@server'/>

A resource may be specified too (as in to='user@server/resource', and is not ignored if present.

A client may do its own probes (only to clients with which it has mutually agreed to share presence information—avoids wanton misuse of the capability). The <presence> message must be fully qualified with from, to, and type=probe attributes.

subscribe

The sending client (identified in the from attribute) wishes to subscribe to the recipient's presence. The recipient is identified in the to attribute.

unsubscribe

The sending client (identified in the from attribute) wishes to unsubscribe to the recipient's presence. The recipient is identified in the to attribute.

subscribed

The sending client (identified in the from attribute) agrees to allow the recipient (identified in the to attribute) to access its presence information. The recipient originated the conversational exchange via a presence message whose type was subscribe.

unsubscribed

The sending client (identified in the from attribute) acknowledges an unsubscribe request from the recipient (identified in the to attribute) to access its presence information. The recipient originated the conversational exchange via a presence message whose type was unsubscribe.

error

An error occurred in the server's processing or delivery of the presence stanza.


The <presence> Subelements

A presence element can have four distinct stanzas as subelements, not all of which are needed in a particular situation.

  • <show/>—This stanza describes the availability of a client in terms of specific values. Other values are ignored. A <show> element typically qualifies a <presence> element whose type attribute is available. A presence element usually contains a <show> child element or a <status> child element but not both, as shown in Table 3.4.

Table 3.4 Complete List of Jabber "Standard" Presence Codes

Value

Significance

away

The client specified in the from attribute is temporarily away from the keyboard.

chat

The client specified in the from attribute is available for "chat." Generally superfluous, as an available client is assumed to be available for chat.

dnd

The client specified in the from attribute has marked its availability as "do not disturb" (dnd = "Do Not Disturb").

normal

The client is available. Once again, generally superfluous.

xa

The client specified in the from attribute is away from the keyboard for an extended period of time. (xa == "eXtended Away").


  • <status/>—An optional natural-language description of availability status. Normally a client issues this as an explicit expression of availability: "I am in a meeting," "I am writing," and so on. It is used in conjunction with the show element to provide a detailed description of an availability state (for example, "In a meeting").

  • <priority/>—The primary purpose of the priority stanza is to suggest to the server which instance of a particular jabberId@jabberServer is the "best" or "first amongst equals" to which a message ought to be routed. Why is this important? Well, suppose you're logged in at your work desk as greenEggs@ham.com, but like many of us you are managing by walking around and you are also logged in on your PDA as greenEggs@ham.com. Now suppose Sam-I-Am@ham.com wants to send you a message. To which presence does the server choose to route a message? If the message is fully qualified with jabberID@jabberServer/Resource, then this might certainly be used to disambiguate. But what if Sam-I-Am is not too brilliant and just addresses a message without a resource? This is where priority comes into play. Clients can set a priority (a non-negative integer) to represent the priority level of the connected resource, with zero as the lowest priority. The higher the number, the more preferred the resource is. Note that although the specification allows a negative priority number to mean that the sender should not be used for direct or immediate contact, not all clients or servers implement this control. Almost all known GUI clients have a control panel to allow setting this value as shown in Figure 3.8.

Figure 3.8Figure 3.8 Setting presence priority via GUI.

  • <error/>—If the server routes a <presence/> message that includes a type="error" attribute, the presence stanza must include an <error/> child element, which in turn should contain a numeric code attribute corresponding to one of the standard error codes listed in Table 3.1 and may also contain PCDATA corresponding to a natural-language description of the error.

Finally, note that a <presence/> message might also contain a namespace-qualified subelement. You as a developer of a client can choose to either ignore these or parse the ones important to your application.

The <message> Element

The message element is the unglamourous "foot soldier" of all the message types. Its attributes and child elements are relatively easy to understand. A minimal message must contain the address elements as attributes on the message tag itself. The to attribute must appear, and a <body> subelement:

<message id='' to='jane@localhost' from='dana@localhost/Home'>
<body>hi</body>
</message>

Again, in a Client, the from is verified against your JID and overwritten if the user@server does not match the JID with which you logged in. It is not incorrect to set and send it, but it is usually best to not send it at all.

Assuming that the two clients were connected to localhost, jane@localhost would receive a message from dana@localhost, and this would result in a message rather than a chat session because no type field has been specified.

If a chat stream had been opened by one of the end-points specifying type=chat as an attribute on the message tag, then a chat window is opened and the message becomes slightly more complex (see Figure 3.9).

Figure 3.9Figure 3.9 Typical message content in a GUI.

In the first message exchange, the chat session is initiated by setting the message type to chat (type='chat'). A thread is established (the thread subelement) to uniquely tag the session between the two clients. More about the <thread/> subelement briefly.

<message id='jcl_7' to='jane@localhost' type='chat' 
from='dana@localhost/Home'>
 <thread>9164572e818c2845f73b442cbc62e1ccf6c3cb48</thread>
 <body>how&apos;s the sailing today?</body>
 <x xmlns='jabber:x:event'>
  <composing/>
 </x>
</message>

For completeness' sake, let's identify the attributes and subelements of <message/> formally.

The <message/> Attributes

A message element can have several attributes, not all of which are appropriate to a given purpose.

  • to—Specifies the message's intended recipient. This attribute is populated by the client initiating the dialogue. In general the JabberID, which is the value on the right side of the equal sign token for the to attribute, is of the form user@server/ resource. The form user@host is often seen too, though, and either resolves to user@host/resource by the Jabber server if the recipient is online, or is directed to offline storage if the user is not online. This attribute is required for all <message/> elements. We've shown several examples already in this chapter.

  • from—Specifies the sender of the message stanza. Normally this is reflected back to a sender as a part of a response to a presence multi-cast managed by the Jabber server's session manager module. In general the Jabber ID for the from attribute must be of the form user@server/resource; however user@server is also acceptable. It may happen that the server might modify or replace the value of this attribute (for example to prevent certain kinds of spoofing), but it's normal practice for you to insert this element in your own client code because it is required for all <message/> elements.

  • id—A unique identifier whose value is a string generated by the Jabber client or client library, and is used by the client to identify the message for tracking purposes (most commonly to correlate messages sent to a group chat room with messages received from the room, or to associate a previously sent message with any errors it might generate). The id attribute is optional.

  • type—Qualifies a message and gives "hints" to a client about what sort of visual interface might be appropriate to display the message. Although the method of portraying the message view is entirely left up to the client, almost all GUI clients provide a different view based on message type. Most interfaces provide an ongoing thread view for chat and groupchat interchanges, and a more "email-like" view for the default message type. For contrast, though, see the unique Lluna client (see "Resources" in Appendix C; see http://www.jabber.org/user/clientlist.php for a comprehensive client list), which uses animated avatars on a Web page. Table 3.5 lists the valid values of the type attribute.

Table 3.5 Complete List of Jabber <message/> Type Attributes

Value

Significance

no value or not present

When nothing is specified, this is a hint that suggests that the receiving client should consider the message as possibly having a "subject" and message "body." Often, clients present this in a separate non-modal window.

chat

This is a hint that suggests that the receiving client should display the message in a typical line-by-line, rolling log chat interface (although the exact interface may be client-specific).

groupchat

This is a hint that suggests that the receiving client should display the message in a "chatroom"-style interface, most famously seen in AOL/AIM or IRC implementations.

headline

Enables you to encapsulate a news article with a title and body sub element. Uses the <x xmlns='jabber:x:oob'> namespace tag.

error

Generally this indicates a response message from the Jabber server. If the value of the type attribute is error, the message should contain an <error/> subelement. This subelement is more fully described later in this chapter.


The <body/> Subelement

The <body/> stanza is a child element of <message/> that carries the message's content. It must not carry any tag attributes. As seen in the earlier "Dead Parrot Sketch" example, notice there's something interesting going on in the actual message data itself contained in the <body></body> tag pair—more about that briefly. Finally, there is an inclusion of XML content from an external namespace (jabber:x:event). This namespace is a standard Jabber namespace that is used to request and respond to message events relating to the delivery, display, and composition of messages. We showed the Jabber namespace more fully in Table 3.2 (earlier), but basically it is one of four events, listed in Table 3.6, that either a client issues when sending a message or that the server issues on a client's behalf.

Table 3.6 jabber:x:event Attachments to a Message

Value

Significance

Offline

Indicates that the server has stored the message offline because the intended recipient is not available. This event is to be raised by the Jabber server.

Delivered

Indicates that the message has been delivered to the recipient. This signifies that the message has reached the Jabber client, but does not necessarily mean that the message has been displayed. This event is to be raised by the Jabber client.

Displayed

After the Jabber client has received the message, it may be displayed to the user. This event indicates that the message has been displayed, and is to be raised by the Jabber client. Even if a message is displayed multiple times, this event should be raised only once.

Composing

In threaded chat conversations, this indicates that the recipient is composing a reply to a message that was just sent. The event is to be raised by the Jabber client. A Jabber client is allowed to raise this event multiple times in response to the same request, providing that a specific sequence is followed.


Therefore, in your code, whenever you add a jabber:x:event extension to a <message/> element, your code as the message sender can track stages in the delivery of that element to its recipient. As shown in the "Dead Parrot Sketch" example, it merely says that the recipient can potentially expect more data later.

XML CDATA in the <body/> Subelement

In Figure 3.9, when jane@localhost replies to dana@localhost, she says: "I'll send you the Annapolis <weather-report-url>." However, when the client generates the message, this is what the message body looks like in raw form:

<message id="jcl_8" to="dana@localhost/Home" type="chat">
 <thread>9164572e818c2845f73b442cbc62e1ccf6c3cb48</thread>
 <body>I &apos;ll send you the Annapolis
 &lt;weather-report-url&gt;</body>
 <x xmlns="jabber:x:event">
 <composing/></x>
</message>

What's going on here? Well, remember that Jabber is XML-based messaging. Therefore the CDATA carried in the message body must properly escape the apostrophe, the quote symbol, the ampersand, the less-than symbol, and the greater-than symbol. Thus your client code can't just send an unexamined message body to the peer on the other side of the conversation. Additionally, the receiving peer must decode the escaped XML as well. Just remember that everywhere in your code that you prepare text for transmission, you need to translate occurrences of special XML characters(&, <, >, and so on) appropriately, and whenever you want to get the textual data from an XML node (for example, the <body/> of a message), you do the opposite.

Fortunately, if you're creating an application atop a client library, the library creator already thought this through for us. For example, in the Python JabberPy package (see "Resources" in Appendix C), the XMLStream handler maintains two simple methods:

def XMLescape(txt):
 "Escape XML entities"
 txt = replace(txt, "&", "&amp;")
 txt = replace(txt, "<", "&lt;")
 txt = replace(txt, ">", "&gt;")
 return txt

def XMLunescape(txt):
 "Unescape XML entities"
 txt = replace(txt, "&amp;", "&")
 txt = replace(txt, "&lt;", "<")
 txt = replace(txt, "&gt;", ">")
 return txt

If you're writing a client from scratch in, say, Python or Java, you should do likewise. Here's another snippet from JabberPy that shows transformation of XML to a string representation suitable for display (for example, in a debug window). Notice that it recurses on itself (line 16), works across namespaces (lines 4-7), and constructs a string representation with escapes properly handled (lines 10, 15, and 18).

01: def _xmlnode2str(self, parent=None):
02:  """Returns an xml ( string ) representation of the node
03:   and it children"""
04:  s = "<" + self.name 
05:  if self.namespace:
06:   if parent and parent.namespace != self.namespace:
07:    s = s + " xmlns = '%s' " % self.namespace
08:  for key in self.attrs.keys():
09:   val = str(self.attrs[key])
10:   s = s + " %s='%s'" % ( key, XMLescape(val) )
11:  s = s + ">"
12:  cnt = 0 
13:  if self.kids != None:
14:   for a in self.kids:
15:    if (len(self.data)-1) >= cnt: s = s +
 XMLescape(self.data[cnt])
16:    s = s + a._xmlnode2str(parent=self)
17:    cnt=cnt+1
18:  if (len(self.data)-1) >= cnt: s = s + 
XMLescape(self.data[cnt])
19:  s = s + "</" + self.name + ">"
20:  return s

Additional <message/> Subelements

In addition to the <body/> subelement, a message stanza may contain zero or one of each of the following stanzas as child elements (which may not contain mixed content):

NOTE

In actual fact, a <message/> is not even required to have a <body/> subelement; The XML specification requires zero or one <body/> subelement. However, it seems to us that it wouldn't be much of a message without saying something—a bit like a sentence consisting of a single period.

  • <subject/>—The message's subject. The <subject/> tag cannot contain attributes.

    <message id='' to='bill@localhost'
     from='dana@localhost/Exodus'>
     <subject>Chapter 10</subject>
     <body>
    How is chapter 10 coming along?
     </body>
    </message>

    Normally if the message's type attribute is set to chat, the client does not provide a capability to set a subject, although nothing in the protocol forbids this. It's simply an idiosyncracy of chatting versus messaging.

  • <thread/>—A random string that is generated by the sender and that may be copied back in replies, although nothing in the protocol absolutely demands that it must be. It is used for tracking a conversation thread. The <thread/> element can not contain attributes.

    <message id='jcl_7' to='dana@localhost' type='chat' 
    from='bill@localhost/Exodus'>
     <thread>0ef291b610e9ec253f0970ebdb0477e1574a4e32</thread>
     <body>The chapter is going fine</body>
     <x xmlns='jabber:x:event'>
     <composing/>
     </x>
    </message>

    In customary client usage, whenever a GUI chat window is closed by either client, a new chat message will generate a new thread value.

  • <error/>—If the message contains an attribute where type="error", the <message/> stanza must include an <error/> child, which in turn must have a standard code attribute corresponding to one of the standard error codes shown earlier, and could also contain PCDATA corresponding to a natural-language description of the error.

    This is demonstrated in the following example, in which client code mistakenly sends a message to a non-existent user (dana@localhost sends a message to william@localhost instead of the correct bill@localhost):

    <message to="dana@localhost" 
    from=william@localhost type="error">
     <body>hi</body>
     <error code="404">Not Found</error>
    </message>
  • <x/> (external) stanzas—A message may also contain any properly-namespaced child element (other than the common PCDATA elements, <stream:stream/> elements, or children of <stream:stream/> elements). This is one way of extending the <message/> element in an arbitrary way. An <x/> stanza can also be used as a protocol element to send commands from server to client or from one client to another. Each time the element is used, a relevant XML namespace must be specified. A single message may contain multiple instances of the <x/> subelement. The namespaces most often occurring in a message with an <x/> subelement are listed in Table 3.7.

NOTE

The <x/> tag is not just a child of <message/>. It is commonly used here, but current work and research is also moving to embedding it in <iq/>, <query/>, and often <presence/> tags.

Table 3.7 Common Extended Namespaces in a <message/>

Namespace

Use

jabber:x:autoupdate

Possible use: Enable arbitrary client-to-application queries about any software updates or version changes available.

jabber:x:delay

Used to provide timestamp information about messages and presence information. Conveyed on presence responses and stored for later delivery when sent to an offline Jabber client. In the latter case, as the client comes back online, this namespace includes information that enables the Jabber client to display the time when the packet was originally sent.

Message example:

<message type='groupchat' from='bill@localhost'>
 <body>Looks like a good day for sailing!</body>
 <x xmlns='jabber:x:delay' 
  stamp='20030313T15:35:43'>Cached</x>
</message>

Presence example:

<presence from='dana@localhost/Exodus'  
        to='bill@localhost'>
 <status>available</status>
 <priority>0</priority>
 <x xmlns='jabber:x:delay' 
 from='dana@localhost/Exodus' 
 stamp='20030314T12:22:14'/>
</presence>

jabber:x:encrypted

Supports exchange of messages encrypted using the public key infrastructure (normally implemented using PGP by the client). A related namespace, jabber:x:signed, is used to support signed messages.

Message example:

<message from='dana@localhost/Exodus' 
 to='bill@localhost/Exodus'>
 <body>This Message is Encrypted</body>
 <x xmlns='jabber:x:encrypted'>
... PGP encrypted message...
 </x>
</message>

jabber:x:oob

Indicates "out of band" data. Enables clients to exchange a standard URI with a description for the purpose of file transfers. URIs exchanged using jabber:x:oob can be included with any message (inside an <x/> subelement) and act as an attachment in the sense familiar from email. Multiple attachments can be included in one message.

Message example:

<message from='dana@localhost/Exodus' 
 to='bill@localhost/Exodus'
 type='chat'>
 <body>URL Attached.</body>
 <x xmlns="jabber:x:oob">
 <url>
 http://java.sun.com/javaone
 </url>
 <desc>
 JavaOne Site
 </desc>
 </x>
</message>

Note: A client may choose to ignore OOB data, in which case nothing is seen at the client.

jabber:x:roster

This namespace allows a user to include roster items within a message, thus making it easy to send contact lists from one user to another. Each roster item is contained in an <item/> subelement within an <x/> element.

Message example:

<message to="bill@localhost" 
 type='chat'
 from='dana@localhost'> 
 <body>My contacts!</body> 
 <x xmlns="jabber:x:roster"> 
 <item jid="jane@localhost" 
  name="jane">  
  <group>Exodus</group>
 </item>
 <item jid="Praline@localhost" 
  name="Mr Praline">
  <group>Friends</group>
 </item>
 </x>

</message>


Using <message/> to Convey Arbitrary Data

In its most familiar usage, Jabber facilitates the movement of short bits of text from one client to the next. What those textual bits mean in any true sense of the word is up to the interpretation of the humans or systems at the end points of the communication with mediation from the client software. From this perspective the Jabber server is "dumb" and clients may grow arbitrarily "smart."

From a practical standpoint, all that client end points have to ensure is that the XML stream they transmit conforms syntactically to the standards described thus far in the book. Message bodies can therefore contain pretty much any data the communicants want to put there. The only proviso is that the message body be contained within a <body/> tag and look like text. Therefore, you could, for example, embed Base64 data representing anything in a message body and have the client programs code and decode the data.

To illustrate this point let's look at an application that is an extreme use to suggest that the outer limits of intended usage are indeed pretty "outer." For this exercise we are going to create an application that shares pictures between roster members. The application works like this: Anytime a client endpoint opens and displays a picture (GIF, JPEG, and so on) from the file system, that picture is transported to the endpoint's roster members. For this exercise, we will assume all roster members are using the same client. The client code shown is a purpose-built client that handles only the intended functionality we have described. It is not a general Jabber textual chat client; as the code will show, it supports only examination of one's roster, encoding of the binary data in an image, and transmission to the roster concurrent with file opening and display.

We have a number of times said that a client need only support as much of the Jabber protocol elements as suit its needs, and this application which mixes in Jabber transport with a picture viewer application illustrates that point well. As a side effect, this example demonstrates that it's relatively easy to add Jabber functionality to general applications. One of our disappointments with many technical books is that code examples are often contrived and don't resemble the approach that you would actually take in coding a real application. Here we've taken a real Ruby language GUI application, stripped it down a bit so that it's easier to present, and then added a few simple lines that turn it into a Jabber client.

NOTE

This example comes from the FOX Ruby site (http://fxruby.sourceforge.net) .FXRuby is a Ruby language extension module that provides an interface to the FOX GUI library. In imageviewer.rb you can see an application that is a model for a typical full-featured GUI application, with a menu bar, toolbar, and so forth.

Consider a group of clients whose relationship may be discerned from Figure 3.10.

Figure 3.10Figure 3.10 A group of clients.

Notice that peer1's roster consists of peer2 and peer3. Peer2's roster consists of peer1. Peer3's roster consists of peer1 also.

In this application, any graphic file that peer1 opens and displays will be transmitted to both peer2 and peer3, replacing whatever was on their local canvases. Any graphic file that peer2 opens and displays will be transmited to peer1, replacing whatever was on its local canvas. Likewise with peer3. Therefore, Figure 3.11 must have been generated by peer1 opening and displaying a picture of a rather wily looking coyote.

Figure 3.11Figure 3.11 Binary messaging application in action.

How does this work? First, Listing 3.1 shows the Ruby code in its entirety, then deconstructs it. To run this code experimentally, you should install a current release of Ruby on your system, then add the jabber4r library. You also need to download the FOX Ruby library. Create some clients sharing rosters, as we showed in the previous figures. From the command line, launch an instance via

ruby bitmapper.rb 'account@host/resource' 'password'

Listing 3.1 Bitmapper.rb

 1:#!/usr/bin/env ruby
 2:
 3:
 4:
 5:require 'jabber4r/jabber4r'
 6:
 7:(puts "bitmapper.rb 'account@host/resource' 'password'")
 & exit if ARGV.size < 2
 8:
 9:
 10:require 'fox'
 11:
 12:include Fox
 13:
 14:class ImageWindow < FXMainWindow
 15:
 16: attr_reader :jabber_session
 17: 
 18: include Responder
 19:
 20: def initialize(app)
 21: # Invoke base class initialize first
 22: super(app, "Jabber Image Sender (#{ARGV[0]}): - untitled",
 nil, nil, DECOR_ALL,
 23:  0, 0, 850, 600, 0, 0)
 24:
 25: # Make some icons
 26: uplevelicon = getIcon("tbuplevel.png")
 27:
 28: # Status bar
 29: statusbar = FXStatusbar.new(self,
 30:  LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER)
 31:
 32: # Splitter
 33: splitter = FXSplitter.new(self, (LAYOUT_SIDE_TOP|LAYOUT_FILL_X|
 34:  LAYOUT_FILL_Y| SPLITTER_TRACKING|SPLITTER_
VERTICAL|SPLITTER_REVERSED))
 35: 
 36: # Sunken border for image widget
 37: imagebox = FXHorizontalFrame.new(splitter,
 38:  FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,
 39:  0, 0, 0, 0, 0, 0, 0, 0)
 40: 
 41: # Make image widget
 42: @imageview = FXImageView.new(imagebox, nil, nil, 0,
 43:  LAYOUT_FILL_X|LAYOUT_FILL_Y)
 44: 
 45: # Sunken border for file list
 46: @filebox = FXHorizontalFrame.new(splitter,
 LAYOUT_FILL_X|LAYOUT_FILL_Y,
 47:  0, 0, 0, 0, 0, 0, 0, 0)
 48: 
 49: # Make file list
 50: fileframe = FXHorizontalFrame.new(@filebox,
 51:  FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,
 52:  0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
 53: @filelist = FXFileList.new(fileframe, nil, 0,
 54:  LAYOUT_FILL_X|LAYOUT_FILL_Y|
ICONLIST_MINI_ICONS|ICONLIST_AUTOSIZE)
 55: @filelist.connect(SEL_DOUBLECLICKED, 
method(:onCmdFileList))
 56: FXButton.new(@filebox, "\tUp one level\tGo
 up to higher directory.",
 57:  uplevelicon, @filelist, FXFileList::ID_DIRECTORY_UP,
 58:  BUTTON_TOOLBAR|FRAME_RAISED|LAYOUT_FILL_Y)
 59:  
 60: # Initialize file name and pattern for file dialog
 61: @filename = "untitled"
 62: @preferredFileFilter = 0
 63: end
 64:
 65: # Convenience function to construct a PNG icon
 66: def getIcon(filename)
 67: FXPNGIcon.new(getApp(), File.open(filename, "rb").read)
 68: end
 69:
 70: def hasExtension(filename, ext)
 71: File.basename(filename, ext) != File.basename(filename)
 72: end
 73: 
 74: def loadImage(file)
 75: file = file.gsub( /\\/ , "/" ) # if you're on WIN32..
 76: sendJabberImage(file)
 77: updateImage(file)
 78: end
 79:
 80: # Load the named image file
 81: def updateImage(file)
 82: img = nil
 83: if hasExtension(file, ".gif")
 84:  img = FXGIFImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 85: elsif hasExtension(file, ".bmp")
 86:  img = FXBMPImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 87: elsif hasExtension(file, ".xpm")
 88:  img = FXXPMImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 89: elsif hasExtension(file, ".png")
 90:  img = FXPNGImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 91: elsif hasExtension(file, ".jpg")
 92:  img = FXJPGImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 93: elsif hasExtension(file, ".pcx")
 94:  img = FXPCXImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 95: elsif hasExtension(file, ".tif")
 96:  img = FXTIFImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 97: elsif hasExtension(file, ".tga")
 98:  img = FXTGAImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 99: elsif hasExtension(file, ".ico")
 100:  img = FXICOImage.new(getApp(), nil, 
IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP)
 101: end
 102:
 103: # Perhaps failed?
 104: if !img
 105:  FXMessageBox.error(self, MBOX_OK, 
"Error loading image",
 106:  "Unsupported image type: #{file}")
 107:  return
 108: end
 109:
 110: # Load it...
 111: getApp().beginWaitCursor do
 112:  FXFileStream.open(file, FXStreamLoad) 
{ |stream| img.loadPixels(stream) }
 113:  img.create
 114:  @imageview.image = img
 115: end
 116: end
 117:
 118: # Quit the application
 119: def onCmdQuit(sender, sel, ptr)
 120: # Quit
 121: getApp().exit(0)
 122: end
 123:
 124: # Command message from the file list
 125: def onCmdFileList(sender, sel, index)
 126: if index >= 0
 127:  if @filelist.isItemDirectory(index)
 128:  @filelist.directory = 
@filelist.getItemPathname(index)
 129:  elsif @filelist.isItemFile(index)
 130:  @filename = @filelist.getItemPathname(index)
 131:  loadImage(@filename)
 132:  end
 133: end
 134: return 1
 135: end
 136: # Create and show window
 137: def create 
 138: puts "creating app"
 139: dir = "." 
 140: @filebox.height = 100 
 141: super # i.e. FXMainWindow::create() 
 142: show(PLACEMENT_SCREEN) 
 143: end
 144:
 145:
 146: 
 147: def connectToJabber(account, account_password)
 148: begin
 149:  @jabber_session = Jabber::Session.bind_digest
("#{account}", "#{account_password}")
 150:  @jabber_session.add_message_listener do |message|
 151:  File.open("TMP"+message.subject, "wb") do |file|
 152:   file.write message.body.unpack("m")[0]
 153:   file.flush
 154:  end
 155:  updateImage("TMP"+message.subject)
 156:  File.delete("TMP"+message.subject)
 157:
 158:  end
 159: rescue
 160:  puts "Account does not exist: #{account}
 - #{account_password}"
 161:  puts $!
 162:  puts $!.backtrace
 163:  exit
 164: end
 165: end
 166: 
 167: def sendJabberImage(imagefile)
 168: puts imagefile
 169: @jabber_session.roster.each_item do |item|
 170:  puts item
 171:  item.each_resource do |resource|
 172:  if resource.name == "bitmapper"
 173:   data = nil
 174:   File.open(imagefile, "rb") {|file| data = file.read}
 175:   data = [data].pack("m")
 176:   @jabber_session.new_chat_message(item.jid.to_s+"/bitmapper")
    .set_subject(File.basename(imagefile)).set_body(data).send
 177:  end
 178:  end
 179: end
 180: end
 181: 
 182: def disconnectFromJabber
 183: @jabber_session.close if @jabber_session
 184: end
 185:end
 186:
 187:if $0==__FILE__
 188: # Make application
 189: application = FXApp.new("ImageViewer", "FoxTest")
 190: # Make window
 191: window = ImageWindow.new(application)
 192:
 193: # Handle interrupts to terminate program gracefully
 194: application.addSignal("SIGINT", window.method(:onCmdQuit))
 195:
 196: # Create it
 197: application.create
 198: 
 199: # Connect to Jabber
 200: window.connectToJabber(*ARGV)
 201:
 202: # Run
 203: application.run
 204: 
 205: # Shutdown Jabber connection
 206: window.disconnectFromJabber
 207: 
 208:end 

Let's begin looking at this example from back to front. First, a block (lines 187-208) outside the ImageWindow class definition says that if we are running this application from the command line (if $0==__FILE__ .. end), create an instance of ImageWindow into the local variable window (lines 189-191). We map an interrupt handler to a method (onCmdQuit) within the instance (line 194). Finally, we give the window its own thread of execution (line 203) and clean up the Jabber connection as the application exits.

 187:if $0==__FILE__
 188: # Make application
 189: application = FXApp.new("ImageViewer", "FoxTest")
 190: # Make window
 191: window = ImageWindow.new(application)
 192:
 193: # Handle interrupts to terminate program gracefully
 194: application.addSignal("SIGINT", window.method(:onCmdQuit))
 195:
 196: # Create it
 197: application.create
 198: 
 199: # Connect to Jabber
 200: window.connectToJabber(*ARGV)
 201:
 202: # Run
 203: application.run
 204: 
 205: # Shutdown Jabber connection
 206: window.disconnectFromJabber
 207: 
 208:end 

During the stream of execution, we import the jabber4r library and check the number of command-line arguments. Execution then passes to the block above.

 1:#!/usr/bin/env ruby
 2:
 3:
 4:
 5:require 'jabber4r/jabber4r'
 6:
 7:(puts "bitmapper.rb 'account@host/resource' 'password'")
 & exit if ARGV.size < 2
 8:
 9:

All of the code associated with the FXRuby extension is provided by the FOX module, so we need to start by requiring this feature:

 10:require 'fox'
 11:

Because all the FOX Ruby classes are defined under the FOX module, you normally refer to them by their "fully qualified" names (that is, names that begin with the Fox:: prefix). To avoid extra finger typing, you add an include Fox statement so that all the names in the FOX module are "included" into the global namespace:

 12:include Fox
 13:

The ImageWindow class inherits from a FOX MainWindow (line 14); standard practice with this toolkit is to subclass your own main window from FOX's MainWindow class and construct its contents in the class's initialize method (lines 20-63; see http:// fxruby.sourceforge.net for toolkit details).

 14:class ImageWindow < FXMainWindow
 15:
 16: attr_reader :jabber_session
 17: 
 18: include Responder
 19:
 20 .. 63

When we load the image for display, we also invoke sendJabberImage(file):

 74: def loadImage(file)
 75: file = file.gsub( /\\/ , "/" )
 # if you're on WIN32..
 76: sendJabberImage(file)
 77: updateImage(file)
 78: end

Let's look at that in more detail. We get the current session's roster and iterate over each roster entry. This is not cached in the jabber_session object, but rather fetched from the server each time to assure currency. (Technically there is some potential from a client to disconnect during this loop, but in this case, the server will simply drop the message.) The roster contains a dictionary of roster items, and each iteration yields one of these, which includes a Jabber ID (Jabber::JID) and a Jabber::Roster::RosterItem::Resource. We check the resources to assure that the client is capable of receiving and displaying images (that is, it must have a resource called bitmapper, line 172).

 167: def sendJabberImage(imagefile)
 169: @jabber_session.roster.each_item do |item|
 170:  puts item
 171:  item.each_resource do |resource|
 172:  if resource.name == "bitmapper"
 173:   data = nil
 174:   File.open(imagefile, "rb") {|file| data = file.read}
 175:   data = [data].pack("m")
 176:   @jabber_session.new_chat_message(item.jid.to_s+"/bitmapper")
    .set_subject(File.basename(imagefile)).set_body(data).send
 177:  end
 178:  end
 179: end
 180: end

We create a Base64-encoded version of the file (line 175), and send it via the jabber_session object. We set the body of the message to the Base64 data (line 176) and send it. The construction of the message, the setting of a subject, and the message send are all chained in this terse call.

Note that even though we send a chat-typed message, we set the subject to a filename. Setting subject in a chat message is unusual, but as we have pointed out previously, it is not forbidden by the protocol. You will see next how this becomes useful at the receiving instance.

On receipt of the message, an instance fires its connectToJabber method. It pulls a suggested filename from the message subject, stores the decoded Base64 data into the file (line 152), then forces the FOX application to read the file and construct and display the image (line 155).

 147: def connectToJabber(account, account_password)
 148: begin
 149:  @jabber_session = Jabber::Session.bind_digest("#{account}",
 "#{account_password}")
 150:  @jabber_session.add_message_listener do |message|
 151:  File.open("TMP"+message.subject, "wb") do |file|
 152:   file.write message.body.unpack("m")[0]
 153:   file.flush
 154:  end
 155:  updateImage("TMP"+message.subject)
 156:  File.delete("TMP"+message.subject)
 157:
 158:  end

We catch exceptions in lines 159-165:

 159: rescue
 160:  puts "Account does not exist: #{account}
 - #{account_password}"
 161:  puts $!
 162:  puts $!.backtrace
 163:  exit
 164: end
 165: end
 166: 

As a part of application cleanup (on exit), we disconnect the session, if one exists (line 183).

 181: 
 182: def disconnectFromJabber
 183: @jabber_session.close if @jabber_session
 184: end
 185:end
 186:

This example uses the very complete jabber4r library, and as you can see, it offers a number of excellent high-level capabilities, including its own thread handling.

We show this example in Ruby because of the language's elegance and succinctness, but it serves to instruct for any other language implementation as well.

If you prefer a Python example, here's the similar application that uses PythonCard, a very nice GUI builder (http://pythoncard.sourceforge.net), one of several GUIs for Python.

It looks slightly different—there are no command-line parameters and there is a separate login screen. We show all source for the three source files (pictureViewer.py, jabberLogin.py, and jabberHandler.py) composing the application for reference in Listings 3.2, 3.3, and 3.4 respectively; however, our comments apply mainly to the JabberHandler.

NOTE

To exercise this example, you need to download both the PythonCard application framework (from http://pythoncard.sourceforge.net) and the wxPython library (from http://www.wxpython.org). Just use the setup.py with each download to install them in the Python library folder.

Figure 3.12 PythonCard ImageViewer implementation.

Listing 3.2 pictureViewer.py

 1:import sys, re, os, string
 2:from PythonCardPrototype import clipboard, dialog,
 graphic, log, model, EXIF
 3:from wxPython import wx
 4:import os, sys
 5:import jabberLogin
 6:from jabberHandler import JabberHandler
 7:
 8:class PictureViewer(model.Background):
 9:
 10: def on_openBackground(self, event):
 11:  self.ignoreSizeEvent = 1
 12:
 13:  self.x = 0
 14:  self.y = 0
 15:  self.filename = None
 16:  self.bmp = None
 17:
 18:  bgSize = self.getSize()
 19:  bufSize = self.GetClientSize()
 20:  widthDiff = bgSize[0] - bufSize[0]
 21:  heightDiff = bgSize[1] - bufSize[1]
 22:  displayRect = wx.wxGetClientDisplayRect()
 23:  self.maximizePosition = (displayRect[0], displayRect[1])
 24:  self.maximumSize = (displayRect[2] - widthDiff,
 displayRect[3] - heightDiff)
 25:
 26:  if len(sys.argv) > 1:
 27:   # accept a file argument on the command-line
 28:   filename = os.path.abspath(sys.argv[1])
 29:   log.info('pictureViewer filename: ' + filename)
 30:   if not os.path.exists(filename):
 31:    filename = os.path.abspath(os.path.join(self.stack.
app.startingDirectory,sys.argv[1]))
 32:   if os.path.isfile(filename):
 33:    self.openFile(filename)
 34:
 35:  if self.filename is None:
 36:   self.fitWindow()
 37:  self.jabberHandler = None
 38:
 39:  self.Show(1)
 40:  
 41:
 42: def on_idle(self, event):
 43:  self.ignoreSizeEvent = 0
 44:  if self.jabberHandler is not None and self.
jabberHandler.isConnected():
 45:   self.jabberHandler.Process()
 46: def on_size(self, event):
 47:  if self.bmp is not None and not self.ignoreSizeEvent:
 48:   oldSize = self.bmp.getSize()
 49:   newSize = self.GetClientSize()
 50:   widthScale = newSize[0] / (0.0 + oldSize[0])
 51:   heightScale = newSize[1] /(0.0 + oldSize[1])
 52:   self.displayFileScaled(widthScale, heightScale, 1)
 53: def sizeScaled(self, size, widthScale, heightScale):
 54:  return ((int(size[0] * widthScale), int(size[1] * heightScale)))
 55: def displayFileScaled(self, widthScale, heightScale, inUserResize=0):
 56:  if self.filename is not None:
 57:   bufOff = self.components.bufOff
 58:   bufOff.autoRefresh = 0
 59:   # figure out new size for window
 60:   size = self.bmp.getSize()
 61:   newSize = self.sizeScaled(size, widthScale, heightScale)
 62:   bufOff.size = newSize
 63:
 64:   if inUserResize:
 65:    self.panel.SetSize(newSize)
 66:   else:
 67:    self.fitWindow()
 68:   bufOff.clear()
 69: 
 70:   bufOff.autoRefresh = 1
 71:   bufOff.drawBitmapScaled(self.bmp, 0, 0, newSize)
 72:
 73:   # attempt to display the file full size if possible
 74: # otherwise the bitmap needs to be scaled
 75: def displayFile(self):
 76:  if self.filename is not None:
 77:   bufOff = self.components.bufOff
 78:   bufOff.autoRefresh = 0
 79:   # figure out new size for window
 80:   bufOff.size = self.bmp.getSize()
 81:   self.fitWindow()
 82:   bufOff.clear()
 83: 
 84:   bufOff.autoRefresh = 1
 85:   bufOff.drawBitmap(self.bmp, 0, 0)
 86: def fitToScreen(self):
 87:  oldSize = self.bmp.getSize()
 88:  newSize = self.maximumSize
 89:  widthScale = newSize[0] / (0.0 + oldSize[0])
 90:  heightScale = newSize[1] /(0.0 + oldSize[1])
 91:  scale = min(widthScale, heightScale)
 92:  self.displayFileScaled(scale, scale)
 93:  self.Center()
 94: def fitWindow(self):
 95:  self.ignoreSizeEvent = 1
 96:  size = self.components.bufOff.size
 97:  self.panel.SetSize(size)
 98:  #if self.ignoreSizeEvent == 1:
 99:  self.SetClientSize(size)
 100: def openFile(self, path):
 101:  self.filename = path
 102:  f = open(path, 'rb')
 103:  tags=EXIF.process_file(f)
 104:  f.close()
 105:  try:
 106:   # the repr() is something like
 107:   # (0x0112) Short=8 @ 54
 108:   # but the str() is just 1, 8, etc.
 109:   orientation = int(str(tags['Image Orientation']))
 110:  except:
 111:   orientation = 1
 112:  self.bmp = graphic.Bitmap(self.filename)
 113:  if orientation == 8:
 114:   # need to rotate the image
 115:   # defaults to clockwise, 0 means counter-clockwise
 116:   #print "rotating"
 117:   self.bmp.rotate90(0)
 118:  elif orientation == 6:
 119:   self.bmp.rotate90(1)
 120:  size = self.bmp.getSize()
 121:  title = os.path.split(self.filename)
[-1] + " %d x %d" % size
 122:  self.SetTitle(title)
 123:  # if either dimension of the image is beyond our maximum
 124:  # then display the image fit to the screen
 125:  if size[0] > self.maximumSize[0] or size[1] > 
self.maximumSize[1]:
 126:   self.fitToScreen()
 127:  else:
 128:   self.displayFile()
 129: def on_menuFileOpen_select(self, event):
 130:  result = dialog.openFileDialog()
 131:  if result['accepted']:
 132:   self.openFile(result['paths'][0])
 133:  if self.jabberHandler is not None and 
self.jabberHandler.isConnected():
 134:   # distribute the picture to all clients on my roster
 135:   self.jabberHandler.sendToRoster(result['paths'][0])
 136: def on_menuFileConnectJabber_select(self, event):
 137:  result = jabberLogin.jabberLogin(self)
 138:  self.jabberHandler = JabberHandler(self,
 jid=result['JabberIdText'], password=result['PasswordText'],
 server=result['JabberServerText'])
 139:  self.jabberHandler.ConnectToJabber()
 140: def on_menuFileSaveAs_select(self, event):
 141:  if self.filename is None:
 142:   path = ''
 143:   filename = ''
 144:  else:
 145:   path, filename = os.path.split(self.filename)
 146:  wildcard = "All files (*.*)|*.*"
 147:  result = dialog.saveFileDialog(None, 
"Save As", path, filename, wildcard)
 148:  if result['accepted']:
 149:   path = result['paths'][0]
 150:   fileType = graphic.bitmapType(path)
 151:   try:
 152:    bmp = self.components.bufOff.getBitmap()
 153:    bmp.SaveFile(path, fileType)
 154:    return 1
 155:   except:
 156:    return 0
 157:  else:
 158:   return 0
 159: def on_menuFileExit_select(self, event):
 160:  self.Close()
 161:
 162:if __name__ == '__main__':
 163: # require JabberID, Password, Server
 164: app = model.PythonCardApp(PictureViewer )
 165: app.MainLoop()

The pictureViewer.py file is a simple frame for displaying image files. From the command line, invoke it via > python pictureViewer.py, as shown in Listing 3.3.

Listing 3.3 jabberLogin.py

 1:from PythonCardPrototype import model, res
 2:import os
 3:
 4:class JabberLogin(model.CustomDialog):
 5: def __init__(self, parent):
 6:  model.CustomDialog.__init__(self, parent)
 7:
 8:
 9: def jabberLogin(parent):
 10:  dlg = JabberLogin(parent)
 11:  dlg.showModal()
 12:  result = {'accepted':dlg.accepted()}
 13:  result['JabberIdText'] = 
dlg.components.JabberIdText.text
 14:  result['PasswordText'] = 
dlg.components.PasswordText.text
 15:  result['JabberServerText'] = 
dlg.components.JabberServerText.text
 16:  
 17:  dlg.destroy()
 18:  return result

JabberLogin.py shown in Listing 3.3 is a simple dialog box for collecting the appropriate connection information from the user. It's shown in Figure 3.12. PictureViewer, the parent frame, calls JabberLogin whenever the user selects Connect to Jabber from the File menu:

(in pictureViewer.py)
 136: def on_menuFileConnectJabber_select(self, event):
 137:  result = jabberLogin.jabberLogin(self)
 138:  self.jabberHandler = JabberHandler(self,
 jid=result['JabberIdText'], password=result['PasswordText'],
 server=result['JabberServerText'])
 139:  self.jabberHandler.ConnectToJabber()

When the GUI user logs into a Jabber connection, the JabberHandler is called. It's the class that implements and encapsulates all the Jabber details for PictureViewer (pictureViewer.py, lines 138–139, and lines 42–45.) Listing 3.4 shows it in its entirety and then we deconstruct its significant implementation in detail.

Listing 3.4 jabberHandler.py

 1:import jabber
 2:import sys
 3:import sha
 4:import sys, re, os, string, base64
 5:
 6:class JabberHandler:
 7: def __init__(self, parent, jid=None, password=None, 
server='localhost', resource="pictureviewer"):
 8: self.parent = parent
 9: self.jid = jid
 10: self.password = password
 11: self.server = server
 12: self.roster = None
 13: self.resource = resource
 14: self.ConnectToJabber()
 15: self.counter = 0
 16: 
 17: 
 18: def ConnectToJabber(self):
 19: self.con = jabber.Client(host=self.server, debug=0,
 port=5222,log=sys.stderr)
 20: try:
 21:  self.con.connect()
 22:  self.connected = True
 23: except IOError, e:
 24:  print "Couldn't connect: %s" % e
 25:  sys.exit(0)
 26: else:
 27:  print "\nConnected\n"
 28: 
 29: self.con.setMessageHandler(self.messageCB)
 30: self.con.setPresenceHandler(self.presenceCB)
 31: self.con.setIqHandler(self.iqCB) 
 32:
 33: if self.con.auth(self.jid,self.password,self.resource):
 34:  print "Logged in as %s to server %s" % ( self.jid,self.server)
 35: else:
 36:  print "ERR -> ", self.con.lastErr, self.con.lastErrCode
 37:  sys.exit(1)
 38: self.con.sendInitPresence()
 39: self.roster = self.con.requestRoster()
 40: summary = self.roster.getSummary()
 41: print "\nRoster:\n"
 42: for name in summary.keys():
 43:  print "\tname—>", name, 
 44:  print self.roster.getOnline(name)
 45:  print "\n"
 46:  
 47: jids = self.roster.getJIDs()
 48: for jid in jids:
 49:  print "JID—>", jid
 50: print "\n"
 51: print "raw =", self.roster.getRaw()
 52:
 53: def isConnected(self): 
 54: return self.connected
 55: def sendToRoster(self, path):
 56:  print "sending to roster", path
 57:  ## get from file and encode...
 58:  f = open(path, 'rb')
 59:  theData = f.read()
 60:  base64Data = base64.encodestring(theData)
 61:  for jid in self.roster.getJIDs():
 62:  print "send pict —> JID ", jid
 63:  msg = jabber.Message(jid,base64Data)
 64:  msg.setSubject(os.path.basename(path))
 65:  self.con.send(msg)
 66:
 67:
 68:
 69: def iqCB(self, iq):
 70:  """Called when an iq is recieved, we just let 
the library handle it at the moment"""
 71:  print "iqCB", str(iq)
 72:
 73: 
 74: def presenceCB(self, con, prs):
 75: """Called when a presence is received"""
 76: print "\npresenceCB\n", str(prs)
 77: who = str(prs.getFrom())
 78: type = prs.getType()
 79: if type == None: type = 'available'
 80: 
 81: # subscription request: 
 82: # - accept their subscription
 83: # - send request for subscription to their presence
 84: if type == 'subscribe':
 85:  print "subscribe request from %s" % who
 86:  con.send(jabber.Presence(to=who, type='subscribed'))
 87:  con.send(jabber.Presence(to=who, type='subscribe'))
 88:
 89: # unsubscription request: 
 90: # - accept their unsubscription
 91: # - send request for unsubscription to their presence
 92: elif type == 'unsubscribe':
 93:  print "unsubscribe request from %s" % who
 94:  self.con.send(jabber.Presence(to=who, type='unsubscribed'))
 95:  self.con.send(jabber.Presence(to=who, type='unsubscribe'))
 96:
 97: elif type == 'subscribed':
 98:  print "we are now subscribed to %s" % who
 99: 
 100: elif type == 'unsubscribed':
 101:  print "we are now unsubscribed to %s" % who
 102: 
 103: elif type == 'available':
 104:  print ("%s is available (%s / %s)" % \
 105:      (who, prs.getShow(), prs.getStatus()))
 106: elif type == 'unavailable':
 107:  print ("%s is unavailable (%s / %s)" % \
 108:      (who, prs.getShow(), prs.getStatus()))
 109: 
 110:
 111: def messageCB(self, con, msg):
 112: #def messageCB(self, msg):
 113:  print "\nmessageCB\n"
 114:  ###
 115: 
 116:  theData = base64.decodestring(msg.getBody())
 117:  suggestedFileName = "TMP"+msg.getSubject()
 118:  print suggestedFileName
 119:  f = file(suggestedFileName, "wb")
 120:  f.write(theData)
 121:  f.close()
 122:  self.parent.openFile(suggestedFileName)
 123:  ## delete the tmp file??
 124: 
 125: def Process(self):
 126: self.con.process(0)

Here we'll deconstruct only the JabberHandler class. The strategy the main application employs is that whenever it has idle cycles, it checks to see whether it has a handle to the JabberHandler and the user has connected to Jabber (if self.jabberHandler is not None and self.jabberHandler.isConnected()). If so, then it calls the JabberHandler.Process() method:

 (in PictureViewr.py)
 42: def on_idle(self, event):
 43:  self.ignoreSizeEvent = 0
 44:  if self.jabberHandler is not None and
 self.jabberHandler.isConnected():
 45:   self.jabberHandler.Process()

This in turn invokes the process() method of the underlying jabber and xmlstream classes (in jabber.py). This way the application can check regularly for incoming messages without blocking UI. A much better way to do this is to spawn a separate thread of execution for the JabberHandler, and use a Python Message Queue for the halves of the application to communicate. For simplicity, we did not do this here, but you should be aware that design strategy for intra-process communication can often have a significant impact on perceived performance.

NOTE

There is a good discussion of this particular topic at http://pythoncard.sourceforge.net/timers-threads.html.

When the GUI user logs into a Jabber connection, this is the class that implements the Jabber handling.

The application imports needed features from the Jabber library and a few others:

 1:import jabber
 2:import sys
 3:import sha
 4:import os, base64
 5:

The constructor needs the parent application's handle (the GUI), a user ID, a server name, and a password. These were all garnered from the GUI and passed to the constructor.

 6:class JabberHandler:
 7: def __init__(self, parent, jid=None, 
password=None, server='localhost', 
      resource="pictureviewer"):
 8: self.parent = parent
 9: self.jid = jid
 10: self.password = password
 11: self.server = server
 12: self.roster = None
 13: self.resource = resource
 14: self.ConnectToJabber()
 15: 

The attempt to connect with the server is implemented here:

 16: def ConnectToJabber(self):
 17: self.con = jabber.Client(host=self.server, 
debug=False, port=5222,log=sys.stderr)
 18: try:
 19:  self.con.connect()
 20:  self.connected = True
 21: except IOError, e:
 22:  print "Couldn't connect: %s" % e
 23:  sys.exit(0)
 24:

We set handlers for the various callbacks, and use our own overrides for the default methods:

 25: self.con.setMessageHandler(self.messageCB)
 26: self.con.setPresenceHandler(self.presenceCB)
 27: self.con.setIqHandler(self.iqCB) 
 28:

If we fail, the application exists. A more mature application might do something such as raise an error dialog and give the user another chance to log in.

 29: if not self.con.auth(self.jid,self.
password,self.resource):
 30:  print "ERR -> ", self.con.lastErr, 
self.con.lastErrCode
 31:  sys.exit(1)
 32: self.con.sendInitPresence()
 33: self.roster = self.con.requestRoster()
 34: summary = self.roster.getSummary()

These lines show off the Jabber library capabilities and are not needed to run the application. When lines 36–41 are executed:

 35:### didactic code, not needed for application
 36: print "\nRoster:\n"
 37: for name in summary.keys():
 38:  print "\tname—>", name, 
 39:  print self.roster.getOnline(name)
 40:  print "\n"
 41:  

the returned result looks like:

Roster:

  name—> Pix2@localhost offline


  name—> Pix3@localhost offline

And this:

 42: jids = self.roster.getJIDs()
 43: for jid in jids:
 44:  print "JID—>", jid
 45: print "\n"
 46: print "raw =", self.roster.getRaw()

produces:

JID—> Pix2@localhost
JID—> Pix3@localhost

as well as the raw roster, which is a Python dictionary of dictionaries. Each JabberId (Pix2@localhost, Pix3@localhost) keys a dictionary of the relevant roster items (status, availability, and so on).

raw = {
u'Pix2@localhost': 
 {'status': None, 
 'name': u'Pix2', 
 'groups': [], 
 'online': 'offline', 
 'ask': None, 
 'show': None, 
 'sub': u'both'
 }, 
u'Pix3@localhost': 
 {'status': None, 
 'name': u'Pix3', 
 'groups': [u'Unfiled'], 
 'online': 'offline', 
 'ask': None, 
 'show': None, 
 'sub': u'both'
 }
}

The sendToRoster method gets a cached roster and then sends the image to roster members:

 50: def sendToRoster(self, path):
 51:  print "sending to roster", path
 52:  ## get from file and encode...
 53:  f = open(path, 'rb')
 54:  theData = f.read()
 55:  base64Data = base64.encodestring(theData)
 56:  for jid in self.roster.getJIDs():
 57:
 58:  msg = jabber.Message(jid,base64Data)
 59:  msg.setSubject(os.path.basename(path))
 60:  self.con.send(msg)

Why did we encode the binary bits (line 55 and also on line 175 of Listing 3.1 the earlier Ruby example), and then pass that to the message constructor?

Well, remember that the data in the body of a message must conform to the rule for ordinary PCDATA. The only reasonable choice is to turn any alien format (alien from the standpoint of XML) into Base64 ASCII data. On receipt, the destination client will simply decode the Base64 data and use it to create a binary object.

We implement a minimal presence handling here, just to keep the roster peers in sync.

 69: def presenceCB(self, con, prs):
 70: """Called when a presence is recieved"""
 71: print "\npresenceCB\n", str(prs)
 72: who = str(prs.getFrom())
 73: type = prs.getType()
 74: if type == None: type = 'available'
 75: 
 76: # subscription request: 
 77: # - accept their subscription
 78: # - send request for subscription to their presence
 79: if type == 'subscribe':
 80:  con.send(jabber.Presence(to=who, type='subscribed'))
 81:  con.send(jabber.Presence(to=who, type='subscribe'))
 82:
 83: # unsubscription request: 
 84: # - accept their unsubscription
 85: # - send request for unsubscription to their presence
 86: elif type == 'unsubscribe':
 87:  self.con.send(jabber.Presence(to=who, type='unsubscribed'))
 88:  self.con.send(jabber.Presence(to=who, type='unsubscribe'))
 89:

We implement a message callback (messageCB) to decode the message body contents (line 92), grab the message subject, use it for a temporary filename (line 93), write the decoded contents of the message (lines 94–96), and notify a method in the parent that there is a new image for display. The parent has a method (self.parent.openFile) that does just this.

 90: def messageCB(self, con, msg):
 91: 
 92:  theData = base64.decodestring(msg.getBody())
 93:  suggestedFileName = "TMP"+msg.getSubject()
 94:  f = file(suggestedFileName, "wb")
 95:  f.write(theData)
 96:  f.close()
 97:  self.parent.openFile(suggestedFileName)

Finally, whenever the parent process invokes its JabberHandler's Process method (pictureViewer.py, lines 42–45), the underlying Jabber process() method fires.

 98: 
 99: def Process(self):
 100: self.con.process(0)

You should use the Pythocard resource editor to produce pictureViewer.rsrc.py and jabberLogin.rsrc.py files corresponding to both the pictureViewer.py and jabberLogin.py files. It would stray too far off topic to discuss this further. If you need additional help in this step (somewhat unlikely,) please email the authors.

Remember before running either the Ruby or Python example to modify your jabber.xml configuration file to allow larger file transfers (see Chapter 2). A "real" application such as this should have to break large binary data into many small messages to better conform to the Jabber model. Sending too large a single message would both overtax the server and possibly even break a client. Remember that the Jabber message model prefers short "IM-style" messages. Additionally, the Jabber server prefers many clients exchanging short messages as well.

NOTE

Also note that the server doesn't respond well to message flooding. Thus, even if you broke the message up into several pieces and tried to send them simultaneously, the server still might complain to your application for violating the karma settings.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020