Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

Wireless messaging with JXTA, Part 1: Using JXTA technology

Integrate J2ME clients in enterprise messaging solutions

Faheem Khan (fkhan872@yahoo.com)Freelance Consultant
Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.

Summary:  Learn how to use JXTA technology to integrate thin Java 2 Platform, Micro Edition (J2ME) clients into enterprise-scale messaging applications by developing a set of classes that let you integrate J2ME clients into JMS (Java Message Service) applications running on Java 2 Platform, Enterprise Edition (J2EE) servers.

Date:  16 Nov 2004
Level:  Introductory

Comments:  

Introduction

In this two-part series, Faheem Khan demonstrates how to use JXTA technology to integrate thin J2ME clients into enterprise-scale messaging applications. While working through this series, you will develop a set of classes that enable you to integrate J2ME clients into JMS applications running on J2EE servers. This article discusses the basic architecture of a typical messaging application and introduces two application scenarios in which you would need to integrate these clients. It also introduces JXTA and explains how to use the JXTA framework to integrate thin clients into JMS applications.


Integrating J2ME devices into messaging applications

Short messaging is fast becoming a significant source of income for cellular network operators. And now, you can use messaging to integrate wireless devices into enterprise applications. With wireless technologies such as J2ME, you can combine the convenience of wireless messaging with wireless programming to let software modules interact with enterprise application servers and exchange messages. This way, your mobile devices can talk to the database servers, provide updates of pending tasks, fetch new instructions, report to the boss, and even perform transactions while on the move.

Consider a courier company that picks up packages from customer doorsteps. It would be useful if the company could notify its field staff to pick up a package while they were operating in a certain area. For this to happen, the company would have to implement wireless messaging and equip its staff with mobile devices capable of exchanging messages with the company's messaging network.

Access to the required information at the right time is critical in modern business. Another simple wireless messaging application can be one that allows your cell phone to log into your enterprise server during a meeting and instantly get the required information. You might also use your cell phone to update your company's enterprise server about any information you obtain during a client visit.


Architectural requirements for messaging

A messaging solution's asynchronous communication mechanism is its most important feature. It ensures that communicating parties don't have to be exclusively committed to each other during communication. In other words, messaging clients don't talk in real time.

Messaging is like sending or receiving letters or e-mails, in contrast to face-to-face or telephonic conversations, which are inherently synchronous in nature (they require real-time commitment). Browser-like communication is also an example of synchronous communication. A browser sends a request to a Web server and then waits to receive the response, unable to do anything else in the meantime. And if the server is unavailable at the time of the request, communication does not occur.

There are two types of messaging clients: senders and receivers. The sender sends a message, and can then go do some other work. It doesn't have to wait for the response from the message's receiver. The receiver retrieves the message at its own convenience.

Messaging clients operate independently of each other. This means that the messaging framework works even if the sender or the receiver is offline. This inherent messaging flexibility offers a great advantage to networked applications that rely on software components sitting across the Internet. Even if some software components are too busy, offline, or temporarily unavailable, the messaging framework still works.

There needs to be a certain level of reliability for messaging. For example, you must make sure that messages don't get lost in transport and are not duplicated at the receiving end. You must keep the messages stored somewhere while clients are offline. The sender might also require the receiver to acknowledge it received the message.

Therefore, there must be some middleware logic that provides these features to messaging clients. The middleware logic is commonly referred to as Message-Oriented Middleware (MOM). MOM implementations provide messaging support to client applications and also perform administrative tasks (such as maintaining a client list as well as queues for each client). The sender client sends a message to MOM and the receiver client later contacts MOM to retrieve the message.


The Java-based messaging solution

JMS is an API that provides a messaging framework to Java applications. Messaging clients can use the JMS API to communicate with the JMS middleware and exchange messages with other JMS clients connected to the same JMS network.

Messaging solution providers implement the JMS API in their application servers (for example, IBM WebSphere®), while application developers rely on the JMS API to develop client applications. This avoids vendor lock-in. It also means that every messaging application will comprise of a JMS-specific set of classes (that are part of the JMS API implementation) and an application-specific set of classes (that implement all application-specific messaging logic). The JMS-specific set of client-side classes provides all the messaging features, such as authoring messages and exchanging them with the JMS middleware.

In JMS terminology, the MOM implementation is referred to as the JMS provider. All clients connect to the JMS provider, as Figure 1 shows. All messaging between the clients is done through the provider. The provider provides various administrative and resource management services; for example, it stores messages from the senders until the receivers fetch their messages. You can say the provider acts as a broker between the message senders and receivers.


Figure 1. JMS clients connected to the JMS provider
JMS clients connected to the JMS provider.

JMS supports both one-to-one and broadcast message styles. One-to-one messaging exists between two communicating parties. The sender sends a message destined for one recipient. On the other hand, broadcast messaging is one-to-many, in which a message from a sender is destined for many recipients.

To coordinate the interaction with messaging clients, the provider uses administered objects. Messaging clients can access administered objects using the Java Naming and Directory Interface (JNDI) API (see Resources for more information). An administrator (one who administers the JMS provider) configures these objects. The messaging client then uses the objects to communicate with other clients through the provider. Let's look at an example to explain how this works.

The administrator configures at least one connection factory (a type of administered object) for the clients connected to the provider. The clients use the connection factory to create connections with the provider. Similarly, the administrator configures destination objects (another type of administered object). The sender clients use these objects to specify their message's target destination. There are two types of destination objects: queues used for one-to-one communication and topics used for broadcast communication. Therefore, a sender can send a message to a queue destination object, if the message is destined for one receiver. Similarly, a sender can send a message to a topic destination object, if it wants to send a message to several receiver clients.

Figure 2 shows the following sequence of events that result in a message sent to a destination:

  1. The administrator creates a queue or a topic destination object on the provider.
  2. A sender client looks up the destination object of his choice.
  3. The sender writes a JMS message.
  4. The sender sends the message to the destination object.

Figure 2. The message-sending sequence
The message-sending sequence

How does the receiver receive the messages sent to a particular queue or topic? First, the receiver makes a connection object using the connection factory on the provider. Next, the receiver creates a topic or a queue connection object (corresponding to the receiver's interest) using the connection created in the first step. Now the receiver can receive messages sent to the particular destination (queue or topic).

Logically, you can have only one receiver connected to queue connections, but any number of receivers connected to topic destinations. In Part 2 of this series, you will see how this works; for example, clients exchange messages with each other through a JMS provider.


J2ME devices as JMS clients

Now, try to determine if a J2ME device can act as a JMS client (a message producer or a consumer). To act as a JMS client, the J2ME device needs to implement the client-side set of JMS functions. This generally includes implementing the support of the following J2ME client features:

  • The ability to use connection factories to establish connections with the provider. This means you need to implement the QueueConnectionFactory and TopicConnectionFactory classes in the J2ME device. This lets you create QueueConnection and TopicConnection objects using their respective connection factories.
  • The ability to create and maintain Queue and Topic objects corresponding to the queues and topics maintained on the provider.
  • The inclusion of J2ME client features such as message authoring, message sending and receiving, and session-related JMS client-side classes.

The J2ME device also must provide a JNDI client, which can hook into a server and ask it for JNDI lookup. The JNDI client enables the J2ME device to look up different administered objects used to communicate with the JMS provider. JMS clients look up connection factories and destination objects on the provider. After a client has a valid connection factory, it creates connections and sessions using the factory. Then, it writes JMS messages and uses the connection and session objects to send and receive messages.

Naturally, you can't expect a J2ME device with just 160 KB of memory to do all this. The JMS API was not designed with low-end, limited-resource devices in mind. Every machine running a JMS client might not be a powerful server, but it will at least be a desktop machine with reasonable processing capability.

The reduced processing capabilities and inherent mobility of a J2ME device make it difficult to run JMS client applications directly on it. However, you can still enable mobile devices to act as messaging clients. You can use a middleware entity (a proxy or a relay) to achieve messaging between mobile clients and enterprise systems.

The J2ME device won't actually need to do any heavy processing. It simply issues orders and commands to the relay, which do all the necessary processing. For example, the J2ME device will ask the relay to perform the JNDI lookup; the relay performs the lookup (for example, communication with the server) and sends the result back to the device. This configuration lets the low-resourced mobile device be part of a JMS network, without performing the heavy processing that normal JMS clients require.

You must design communication data formats to allow the J2ME device to issue commands to the relay and to accept the results. Although you can design all the required data formats from scratch, you don't need to here. You can use JXTA data formats for communication between a J2ME device and the relay. This way, instead of designing a proprietary set of data formats from scratch, you can use the framework of an existing technology to integrate J2ME devices into JMS networks. Moreover, you already have J2ME and relay-side open source implementations of JXTA communication available, which you can leverage for your purpose.

Shortly, I will start digging into the details of JXTA communication, but before I do, there's another small problem to discuss.


Network address-binding concepts

A J2ME device's reduced processing capability is not the only problem that makes it difficult to connect to a JMS network. Mobile devices are inherently non-static and might continue to change their physical network addresses. For example, a cell phone might be connected to different service providers depending on its current location. Therefore, you might not be able to allocate a fixed physical address to a J2ME device. To locate a physically static networked device, you can assign a permanent address to each one. This method, known as static or early network binding, allows devices to communicate with each other by using the target device's permanent network address.

The most common example of using early network bindings is the way Internet service providers (ISPs) provide connectivity to Internet users. An ISP has many IP addresses reserved for its users and simply assigns an IP address to each of its currently logged-on users. When a user sends a request (for example, the request for a Web site), its response is routed to the ISP, which in turn sends the response back to the requesting user.

But what happens if a user (for example, a cell phone user connected to the Internet) periodically changes its ISP or IP address? In this case, some responses destined for the requesting user might never reach him or her. For such users, you need some mechanism to locate the network address bindings dynamically. This is the concept of dynamic or late network address bindings. Dynamic address bindings are more complicated than static address bindings because the addresses change as the position and state of the target devices in the network changes.

JXTA provides stable and reliable connectivity to mobile devices that have dynamic network address bindings. It uses a layer of virtualization to cover up the network dynamism. Later in the article you'll see how JXTA accomplishes this.

To solve the problem of dynamic network connectivity, JXTA offers a special arrangement. The J2ME client always requests something from the server (the server never initiates contact). If the server needs to contact the client (for example, with the result of an earlier client command or order), it keeps its messages in a queue and waits for the client to contact or poll the server. When the client polls, the server sends queued messages to the client.

JXTA already has an entity that can work as your middleware component for reducing the processing burden on the mobile devices as well as for providing dynamic address binding. This entity is called a JXTA relay; let's see how it works.


Introducing JXTA

JXTA defines a set of protocols to enable an open framework for peer-to-peer computing. It provides a virtual layer over the network layer. This virtual layer works through the exchange of XML messages between the different JXTA network users.

The protocols defined by JXTA are independent of implementations. You can implement the protocols for any platform using any network topology and any type of network transport. This is why application developers can develop value-added JXTA applications by depending on the set of protocols. In fact, the idea of integrating a J2ME client into a JMS network using JXTA technology is an example of a value-added JXTA application.

The main objective for having a virtual layer is to hide physical addresses and low-level networking details from other entities in the network. Instead, all users and entities in the JXTA network have identifiers. The JXTA network can resolve identifiers to physical network addresses dynamically on the fly at run time. Therefore, JXTA network users are not identified by their network addresses.

All JXTA network users are peers to each other. Normally, different JXTA applications running across a JXTA network act as peers. Peers communicate with each other to perform different tasks (such as searching for new peers with common interests). Peer identifiers uniquely identify the peer on the JXTA network, and do not change from one session to the next. No matter which physical address (for example, an IP address) you use for communication over the JXTA network, your peer identifier remains the same.

Therefore, if you are trying to search for a friend over the JXTA network, you just need to know the peer identifier. No matter what IP address the friend is currently using, you will reach them if they are logged onto the JXTA network using their peer identifier. This is the main advantage of having this identifier-based virtualization.

JXTA also allows peers to join peer groups. Peers with common interests are logically grouped together. For example, if you are a JXTA peer with an interest in a particular sport, you might want to join other peers interested in the same sport. In a sports peer group, you might want to share sports news, exchange information with other peers, or do some collaborative work. Peer groups can also share the processing burden of computationally intensive tasks. And just like individual peers, peer groups are identified using identifiers. A peer can join any number of peer groups.

According to the JXTA set of protocols, every peer must join at least one peer group; otherwise, the peer cannot participate in any JXTA messaging. If a peer does not want to join any particular peer group, the peer joins a universal peer group called NetPeerGroup.

Both peers and peer groups work from the idea of advertisements. JXTA advertisements are XML documents that specify the details of peers, peer groups, and other JXTA resources. A JXTA advertisement contains useful information about a JXTA resource (including the name of the resource or service, its identifier, and description).

Peers specify their service details using these JXTA advertisements. For example, if you want to offer information regarding a particular topic, you would publish an appropriate advertisement over the JXTA network. Other peers search for your advertisements and discover you as a peer as well as the services you are hosting or offering. Naturally, advertisements are not perpetual. They have a lifetime, after which they expire.

Although I do not discuss the details of JXTA advertisements in this series, I do need to mention that all entities in a JXTA network know that other entities exist through their advertisements. Resources contains more details about the JXTA advertisements.

JXTA defines two special types of peers that play important roles in JXTA communication: rendezvous peers and router peers. Rendezvous peers provide a meeting point for peers. Peers can discover other peers by contacting rendezvous peers, who keep the advertisements cached with them. Therefore, you can send your advertisements to rendezvous peers and also search for other peers and peer groups (as well as the services they offer) at rendezvous peers.

Router peers store routing information to keep track of how to reach remote peers. Sometimes it's difficult to find a direct connection or network route from one peer to another; this is where router peers can help.

Apart from peers and peer groups, JXTA also defines another important resource known as a JXTA pipe. Pipe is a virtual communication channel used for communication between different peers. Pipes are also identified using identifiers, just like peers and peer groups. JXTA defines several types of pipes. I will use only two types of pipes: unicast and propagate.

Unicast pipes are used for communication between two peers (a sender and a receiver). A propagate pipe is used for communication between more than two peers (one sender and many recipients). Notice the resemblance between a JXTA unicast pipe and a JMS queue. Similarly, a JXTA propagate pipe is analogous to a JMS topic. You will see this parallel in Part 2 of this article, where I use JXTA for integrating J2ME clients into JMS networks.

Pipes are unidirectional, which means you can use a pipe either to send data from one end to the other or to receive data coming from the opposite end. When you want to receive incoming messages you first need to create a pipe. After you create a pipe, you open it for input and start listening for incoming messages from other peers. After you successfully open the pipe for input, the relay accepts messages and stores them until you're ready to retrieve them.

On the other hand, if you want to send a message to a peer over a pipe, you need to first search for your peer's pipe. After you find it, you can send the message. Assuming the peer is listening at the pipe, the peer will receive your message.

J2ME and the JXTA relay

The JXTA relay can accept client commands and act upon the commands on the client's behalf. The relay acts as a junction between the JXTA network and peers (those that cannot directly communicate with the JXTA network). A J2ME client uses relays to share the XML authoring and processing burden.

JXTA has defined the data communication protocols that enable messaging between a relay and a client. It is worthwhile to note that a JXTA relay is not just meant to serve J2ME clients. A relay can serve any client that can communicate according to the specified protocols.

In any case, JXTA has provided a J2ME-based client-side implementation that implements all communication with the relay. It's called JXTA for J2ME, or JXME for short. Using JXME with a relay lets J2ME clients act as JXTA peers.

The JXTA relay receives commands from a J2ME client, perform what's necessary on the client's behalf, and represents a JXME client on the JXTA network. The JXME client sends commands and messages to the relay. The relay executes the command on the JXTA network and returns the results back to the JXME client. For example, Figure 3 shows the following sequence of events:

  1. A JXME client sends a request to a relay to create a pipe.
  2. The relay performs all the steps to create a JXTA pipe (for example, authoring the pipe advertisement and sending the advertisement to known rendezvous peers).
  3. The relay returns the newly created pipe's identifier to the JXME client.

Figure 3. JXTA relay -- JXME client interactions
JXTA relay -- JXME client interactions

Messaging between a relay and a JXME client

The JXME client sends several types of messages to the relay to perform different tasks. JXTA has defined a special message format that a mobile device uses to communicate with the relay. Before looking at the actual communication, I need to discuss the message format. Figure 4 shows the graphical representation of a typical message.


Figure 4. Representation of a JXME message
Representation of a JXME message

As you can see, a JXME message contains basically two things: a message header and a number of elements that give the message its actual meaning. Figure 4 graphically shows an element's different fields as well as the arrangement of a JXME message's different elements.

Both the header and each individual element of a JXME message consist of various fields, which I'll discuss. First, take a look at the JXME message's textual representation. Listing 1 shows the JXME message that Figure 4 graphically represents.


Listing 1. Textual representation of a typical JXME message
jxmg      0     01    05  proxy                         07
jxel      2     0     07  request                       0006  create
jxel      2     0     04  name                          0004  test
jxel      2     0     09  requestId                     0001  1
jxel      2     0     04  type                          0004  PIPE
jxel      2     0     03  arg                           0007  Unicast
jxel      1     0     26  EndpointDestinationAddress    xxxx  destination address
jxel      1     0     21  EndpointSourceAddress         xxxx  source address

In actual practice, you would place all the data in a JXME message's single line, but Listing 1 shows the header and each individual element on a separate line for better readability.

You can compare Figure 4 and Listing 1 to find the values of the figure's different fields. The first line in Listing 1 is the message header. The different fields in the header are explained below:

  1. All JXME messages start with the string jxmg. This is shown as the message signature field in Figure 4's header.
  2. The 0 after jxmg specifies the JXME message's version number.
  3. The header's third field is a two-byte value, which is 01 in Listing 1. This value specifies the number of namespaces that this JXME message uses. In Listing 1, I have used only one namespace (which I will define in the header's fourth field). The message's different elements reside inside some namespace and each element can belong to a different namespace. If you are familiar with the concept of namespaces in XML, you can see that JXME messages use the same concept: each element in a JXME namespace belongs to a certain namespace; if it does not, you can assume the element belongs to a so-called empty or default namespace.
  4. The fourth field in the header provides the declaration of namespaces used by this message. The namespace declaration field starts with a two-byte integer value (05 in Listing 1), which specifies the number of characters in the name of the first namespace (proxy). The actual name of the namespace (proxy) follows the two-byte integer value.

    Because you can have any number of namespaces in a JXME message, the fourth field can contain any number of namespace declarations. Each namespace declaration is a pair of two-byte values and a string that specifies the namespace name.

    Notice that the order of namespaces in the header is important. The first namespace (proxy in Listing 1) is automatically assigned an identifier 2. All subsequent entries get an identifier after an increment of 1 to the previous identifier. For example, if I create another namespace, say ourNamespace, it would have an identifier 3.

    The identifiers 0 and 1 are preassigned to an empty namespace and jxta namespace respectively.

    I use namespace identifiers (instead of actual names) in the JXME message's individual elements. I map the namespace names to integer identifiers in order to reduce the message size. This optimizes the use of bandwidth in wireless communication.

  5. The last two bytes 07 specify the number of elements in the message. As you can see in Listing 1, I have seven elements after the message header.

I've noted that Listing 1 includes seven elements. Each element inside a message conveys certain information. The different elements of a message together provide meaningful information to the message's recipient. For example, this information could be a request to create a peer group or to join one. The following points explain an individual element's different fields:

  1. Each element starts with a signature jxel, which indicates the start of a message element.
  2. The identifier of the element's namespace follows the element signature. In the message's first five elements, the field's value is 2, which specifies the proxy namespace (recall the header's fourth field, where I defined 2 as the identifier for proxy namespace). The namespace identifier 1 in Listing 1's last two elements specifies the predefined namespace jxta.
  3. Next to the namespace identifier is a one-byte flag value. This byte value's bits act as flags and specify the optional fields (such as MIME type and encoding) in the element. A value of 0 for this field means that I will use default values for the optional fields (the default MIME type is application/octet-stream and the default content encoding is base64).
  4. The element's name follows the flag byte. The name actually consists of two subfields; first is a two-byte value (07 in the first element) that specifies the number of characters in the element name. The length value is followed by the element name itself (request in the first element).
  5. The element name is followed by the element's actual contents. The contents field consists of two subfields: a four-byte value that specifies the number of characters in the contents field (0006 in the first element) and the content itself (create in the first element).

Now, take a look at the various messages a mobile client sends to the relay to perform different operations. When a mobile client connects to a JXTA network, it first requests a peer identifier. The relay assigns a peer identifier in response to the request, and the client uses the peer identifier in subsequent communication. After the mobile client has the peer identifier, it requests a connection with the relay, and the relay confirms a connection in a response message. Figure 5 illustrates this sequence.


Figure 5. Request-response sequence for a JXME client entering the network for the first time
Request-response sequence for a JXME client entering the network for the first  time

After a connection is established, a JXME peer can issue other requests to the relay to carry out its operations. Now, look at the messages in detail.

Requesting a peer identifier

As shown in Listing 2, the mobile client sends the request for a peer identifier as a URL in an HTTP message using the HTTP-GET method. It stores the peer identifier that the relay returns for subsequent use.


Listing 2. The request for a peer identifier
GET /unknown-unknown?0,-1,http://172.16.0.37:2481/
EndpointService:jxta-NetGroup/
uuid-DEADBEEFDEAFBABAFEEDBABE0000000F05/pid HTTP/1.1
Connection: close
Content-Length: 0
User-Agent: UNTRUSTED/1.0
Host: 172.16.0.37:2481

The URL in the HTTP request is shown below:

unknown-unknown?0,-1,http://172.16.0.37:2481/
EndpointService:jxta-NetGroup/uuid-DEADBEEFDEAFBABAFEEDBABE0000000F05/pid

Examining this URL, you find the following components:

  1. unknown-unknown -- This specifies that the requestor doesn't have a peer identifier.
  2. 0 -- This is the default time-out value in milliseconds. A value of 0 means that the client waits until it receives a response.
  3. -1 -- This is how long (in milliseconds) the JXME peer stays connected to the relay after the response to a peer identifier request has arrived. The value -1 means that the client disconnects immediately after receiving the response. This value is always -1 for JXME clients.
  4. http:// 172.16.0.37:2481-- This is the IP address and the port number where the relay is listening.
  5. EndpointService:jxta-NetGroup -- This part is an identifier for the relay. This value is always the same in all JXME requests for peer identifiers.
  6. uuid-DEADBEEFDEAFBABAFEEDBABE0000000F05 -- This is a class identifier for the relay service. JXTA has defined class identifiers for different types of JXTA services.
  7. pid -- The last part specifies the actual command string. Here pid means that the client is asking for a new peer identifier.

Listing 3 shows a typical response from the relay to the above request. The relay sends the response as a JXTA message packed inside the HTTP response body.


Listing 3. A peer identifier response
jxmg    0     01    05 relay                           04
jxel    2     0     08 response                        0003  pid
jxel    2     0     06 peerid                          xxxx  peer identifier
jxel    1     0     26 EndpointDestinationAddress      xxxx  destination address
jxel    1     0     21 EndpointSourceAddress           xxxx  source address

The response contains four elements, two each in the relay and jxta namespaces:

  1. The first element specifies that it is a response to a peer identifier request.
  2. The second element specifies the peer identifier assigned to the peer.
  3. The third element tells the endpoint destination address. In this case, the destination is the JXME client.
  4. The last element specifies the endpoint source address. In this case, the source is the relay.

Requesting a connection

After the client requests a peer identifier, it requests a connection with the JXTA relay. Listing 4 shows a typical connection request, which is sent as an HTTP request message using the HTTP GET method.


Listing 4. Connection establishment request
GET 
/uuid-59616261646162614A78746150325033F55704B199754D3E9076A2947775A6EE03?0,
-1,http://172.16.0.37:8010/
EndpointService:jxta-NetGroup/uuid-DEADBEEFDEAFBABAFEEDBABE0000000F05/
connect,3600000,keep,other HTTP/1.1
Connection: close
Content-Length: 0
User-Agent: UNTRUSTED/1.0
Host: 172.16.0.37:8010

Listing 4 shows that you can easily extract the URL from the request.

uuid-59616261646162614A78746150325033F55704B199754D3E9076A2947775A6EE03?0,
-1,http://172.16.0.37:8010/EndpointService:jxta-NetGroup/uuid-DEADBEEFDEAFBABAFEEDBABE
0000000F05/connect,3600000,keep,other

This request is very much like the peer identifier request, with a few important things to note:

  1. The URL starts with a valid peer identifier (the same identifier that you received in response to the peer identifier request).
  2. You use a different command string, connect, to request a connection. In the request for a peer identifier the command string was pid.
  3. The value after the command string 3600000 tells the time in milliseconds that the relay will store the incoming messages for the JXME client. If the client does not retrieve its messages within the specified time, the relay discards them.

In response to this message, a relay might send back information shown in Listing 5, which tells you that the connection has been successfully established.


Listing 5. A connection response
jxmg      0      01      05  relay                          04
jxel      2      0       08  response                       0009  connected
jxel      2      0       05  lease                          0007  3600000
jxel      1      0       26  EndpointDestinationAddress     xxxx  destination address
jxel      1      0       21  EndpointSourceAddress          xxxx  source address

After the client connects with the relay, you can perform the following operations:

  • Joining a peer group
  • Searching for resources
  • Creating a pipe
  • Sending messages over a pipe
  • Opening a pipe for input

Joining a peer group

To join a peer group, the client sends a join request message to the relay. The request and response sequence is depicted in Figure 6.


Figure 6. A request for joining a peer group and its response
A request for joining a peer group and its response

Listing 6 shows a typical join request.


Listing 6. The request for joining a peer group
jxmg     0      01      05  proxy                         06
jxel     2      0       07  request                       0004  join
jxel     2      0       02  id                            xxxx  peer group identifier
jxel     2      0       03  arg                           xxxx  password
jxel     2      0       09  requestId                     0001  1
jxel     1      0       26  EndpointDestinationAddress    xxxx  destination address
jxel     1      0       21  EndpointSourceAddress         xxxx  source address

This request message is composed of six elements. The first four elements belong to the proxy namespace and the last two belong to the jxta namespace. The message's different elements are explained below:

  1. The request element specifies the request's purpose (joining a peer group).
  2. The id element holds the peer group identifier.
  3. The arg element specifies a password required to join the peer group. This helps to authenticate a mobile peer on the peer group. If no password is supplied (for example, when it's not required), an empty string is sent as this element's value.
  4. The requestId element holds an integer associated with the request and is used to match responses from the relay. Notice that the relay's responses come in any order; therefore, you need message identifiers to match requests with responses.
  5. The EndpointDestinationAddress and EndpointSourceAddress elements, respectively, specify the addresses of the relay and the requesting client.

Listing 7 shows the message that the relay sends back in response to the request to join a peer group.


Listing 7. The response to a join peer group request
jxmg      0        01      05  proxy                          04
jxel      2        0       08  response                       0007  success
jxel      2        0       09  requestId                      0001  2
jxel      1        0       26  EndpointDestinationAddress     xxxx  destination address
jxel      1        0       21  EndpointSourceAddress          xxxx  source address

The response element in the message shown in Listing 7 indicates that the request was successfully met and the requesting client has joined the new group, which means it can proceed to operate in that group.

Searching for resources

You use the search request messages to search for pipes, peers, peer groups, and other JXTA resources. Figure 7 represents the search request and its responses from the relay. The number of messages a search request might return depends on the number of resources that successfully match the search criteria. This is represented by a dashed response line in Figure 7.


Figure 7. A request for searching a resource and its response
A request for searching a resource and its response

Now, take a look at a sample search message that issues a search request for a peer group.


Listing 8. The request for searching a peer group
jxmg      0        01      05  proxy                        08
jxel      2        0       07  request                      0006  search
jxel      2        0       04  type                         0005  GROUP
jxel      2        0       04  attr                         0004  name
jxel      2        0       05  value                        0003  myPeerGroup
jxel      2        0       09  threshold                    0001  1
jxel      2        0       09  requestId                    0001  3
jxel      1        0       26  EndpointDestinationAddress   xxxx  destination address
jxel      1        0       21  EndpointSourceAddress        xxxx  source address

I'll explain the several elements in this message one by one:

  1. The request element specifies that it is a search request.
  2. The type element specifies the type of the resource that is searched. The contents of the type element in Listing 8 include GROUP, which means you are searching for a peer group. Other values of this field might be PIPE (if you are searching for a pipe) and PEER (if you are searching for a peer).
  3. The attr element specifies the attribute of the searched resource. For example, if you are searching for a particular peer group by name, the content of the attr element is name, as shown in the listing above.
  4. The value element specifies the search query. For example, if you are searching a peer group with a particular name (for example, myPeerGroup), you specify the name as contents of the value element. You can also use the wildcard character (asterisk, *) in the query string to create pattern-matching queries.
  5. Different JXTA peers are expected to send messages in response to the search query. The threshold element is used to limit the number of responses that a single peer can send in response to the search query.

Each response of the search message comes as a separate message. Listing 9 shows a typical response message to the search request.


Listing 9. Response for group search
jxmg      0       01      05 proxy                         07

jxel      2       0       08 response                      0006  result
jxel      2       0       04 type                          0005  GROUP
jxel      2       0       04 name                          xxxx  name of group found
jxel      2       0       01 id                            xxxx  id of group found
jxel      2       0       09 requestId                     0001  2
jxel      1       0       26 EndpointDestinationAddress    xxxx  destination address
jxel      1       0       21 EndpointSourceAddress         xxxx  source address

This response contains a single search result. The type element specifies the type of resource searched (peer group), the name element specifies the resource name, and the id attribute specifies the peer group identifier. The requesting client can now directly use the identifier for further communication with the peer group. For example, the client can now use the peer group identifier to join the group.

Creating a pipe

As previously mentioned, when you want to receive incoming messages over the JXTA network you first need to create a pipe. Other peers use your pipe to send messages to you. Figure 8 depicts the pipe creation request and its response from the relay.


Figure 8. A request for creating a pipe and its response
A request for creating a pipe and its response

Listing 10 shows what a request message for creating a pipe looks like.


Listing 10. Pipe creation request
jxmg      0       01      05  proxy                        07
jxel      2       0       07  request                      0006  create
jxel      2       0       04  name                         0008  testPipe
jxel      2       0       09  requestId                    0001  4
jxel      2       0       04  type                         0004  PIPE
jxel      2       0       03  arg                          0011  JxtaUnicast
jxel      1       0       26  EndpointDestinationAddress   xxxx  destination address
jxel      1       0       21  EndpointSourceAddress        xxxx  source address

The request is composed of seven elements:

  1. The first element specifies that it is a request to create a resource.
  2. The second element tells the name of that resource.
  3. The requestId is an integer associated with the request, which the request author uses to match responses from the relay.
  4. The type element specifies the resource's type (pipe, in this case). If you were creating a peer group, the value of this field would have been GROUP.
  5. The arg element specifies the pipe's type. For JXME clients, this is either Unicast or JxtaPropagate.
  6. The sixth and seventh elements specify the destination and source endpoint addresses respectively.

Listing 11 shows the message from the relay confirming a new pipe was created.


Listing 11. Pipe creation response
jxmg      0      01     05  proxy                        08
jxel      2      0      08  response                     0007  success

jxel      2      0      09  requestId                    0001  4
jxel      2      0      04  type                         0004  PIPE
jxel      2      0      04  name                         0008  testPipe
jxel      2      0      02  id                           xxxx  pipe identifier
jxel      2      0      03  arg                          0011  JxtaUnicast
jxel      1      0      26  EndpointDestinationAddress   xxxx  destination address
jxel      1      0      21  EndpointSourceAddress        xxxx  source address

Most of the response message elements are similar to their counterparts in the request message. In fact, there are only two main differences:

  1. The first element tells you that this is a response message and its content indicates that the request was completed successfully.
  2. The id element gives you the identifier of the newly created pipe.

The peer group creation request messages are very similar to the pipe creation request messages. The only difference is in the type element. If you want to create a new peer group, the content of the type attribute is GROUP. Also, you don't need the arg element to create a peer group request.

Opening a pipe for input

Now, take a look at how to open a pipe for input (called listening in JXME terminology) in order to receive messages from other peers. To open a pipe for input, you need to send a listen request message to the relay. The relay starts listening for your messages, which you can retrieve later.

Figure 9 shows the request/response graphical representation.


Figure 9. The request and response sequence for opening a pipe for input
The request and response sequence for opening a pipe for input

Listing 12 shows a typical listen request message.


Listing 12. Input pipe open request
jxmg      0      01     05  proxy                        05
jxel      2      0      07  request                      0006  listen
jxel      2      0      02  id                           xxxx  pipe Id
jxel      2      0      09  requestId                    0001  1
jxel      1      0      26  EndpointDestinationAddress   xxxx  destination address
jxel      1      0      21  EndpointSourceAddress        xxxx  source address

The request element in Listing 12 specifies that the request is to listen on a certain pipe. The id element specifies the identifier of the pipe on which the peer wants to listen for messages.

Listing 13 shows the relay's response to this message. It says that the request was successful, which means that the relay listens for messages sent on this pipe and stores them until retrieved.


Listing 13. The relay's response to a listen request
jxmg      0      01     05  proxy                        04
jxel      2      0      08  response                     0007  success
jxel      2      0      09  requestId                    0001  2
jxel      1      0      26  EndpointDestinationAddress   xxxx  destination address
jxel      1      0      21  EndpointSourceAddress        xxxx  source address

Sending a message over a pipe

Before you can send a message to a pipe, you first have to search for that pipe. A pipe search message is very similar to the peer group search message, so I won't go through it again.

Figure 10 shows that sending a message over a pipe is a simple request-response procedure.


Figure 10. The request and response for sending a message over a pipe
The request and response for sending a message over a pipe

Listing 14 shows a message addressed to a specific pipe.


Listing 14. The request for sending a message
jxmg      0      02     05  proxy 07 records             08
jxel      2      0      07  request                      0004  send
jxel      2      0      09  requestId                    0001  2
jxel      2      0      02  id                           xxxx  target pipe-Id
jxel      0      0      06  sender                       0006  tester
jxel      0      0      07  message                      0005  Hello
jxel      3      0      05  fileReference                0001  3
jxel      1      0      26  EndpointDestinationAddress   xxxx  destination address
jxel      1      0      21  EndpointSourceAddress        xxxx  source address

I have already explained the first three elements (request, requestId, and id). The sender and message elements belong to the empty namespace (the namespace identifiers for these elements is 0). These two elements are application-specific, which means you can invent your own elements to go with the message being sent to a pipe.

You can also define your own namespace and include an application-specific element that belongs to your namespace in the message. For example, look at the element named fileReference, whose namespace identifier field has a value 3. The header of the message defines the namespace as records.

Listing 15 shows the relay's response to this message. The response element in Listing 15 says that the request was successful, which means that the relay has accepted the message for delivery. Now the relay must dispatch the message to its destination.


Listing 15. The response for a message send request
jxmg      0      01     05  proxy                        04
jxel      2      0      08  response                     0007  success
jxel      2      0      09  requestId                    0001  2

jxel      1      0      26  EndpointDestinationAddress   xxxx  destination address
jxel      1      0      21  EndpointSourceAddress        xxxx  source address

Retrieving the response messages

JXME communication with the relay is asynchronous in nature, which means the relay does not send the response to a JXME's client request or a message immediately. Other peers respond at their convenience and the relay stores incoming messages for the JXME client. The JXME client contacts the relay (a mechanism called polling) to receive its incoming messages.

This means that the JXME client always contacts the relay; the relay never initiates a connection. This is especially important because a J2ME device can only act like an HTTP client (by sending HTTP requests) and cannot act like an HTTP server (by listening for requests coming from clients).

JXME does not define any special request message that explicitly asks the relay to send one or more response messages it has for the JXME client. The JXME client continues to send requests (for example, a peer group join request, a pipe creation request, or a search query) or other outbound messages to the relay. The relay avails these requests as opportunities to send any incoming message to the JXME client in response to a request. The JXME client uses the requestId element in the response message to find out which request this response corresponds to.

What happens if the JXME client has no outbound message to send to the relay, but still wants to check for incoming messages? In this scenario, the client can send a simple HTTP request. The HTTP request doesn't contain any JXTA message or cause any processing at the relay's end. The relay simply sends an incoming message to the client in response to the request.

In the next section, I'll go through the programming concepts required to implement JXTA-enabled applications.


JXME programming

I've discussed messaging between a JXTA relay and a mobile client. Now, it's time to demonstrate how to programmatically implement this messaging into a J2ME client. I don't need to implement the low-level details of JXME messaging because it is already available from the JXTA project Web site (see Resources for a direct link to the JXTA downloads Web site).

The JXME implementation consists of three main classes:

  • The Element class
  • The Message class
  • The PeerNetwork class

The Element class

The Element class represents a single element of the JXME message. You can use the Element class to author individual elements of a JXME message. The JXME implementation uses the Element class to author JXME messages. On the other hand, you use this class to author the elements of your own (customized) JXME messages.

When you want to author a JXME message's element, you instantiate an Element object. The Element constructor takes four parameters, as shown below.

Element e = new Element(
               "message",
               "hello!".getBytes(),
               "peerNamespace",
                null); 

The first parameter ("message") specifies the element name. The second parameter ("hello!".getBytes()) contains the byte array, which represents the element's contents. The third parameter ("peerNamespace") carries the namespace string (an empty string means the default or empty namespace). The last parameter (null) specifies the MIME type of the data (null means the default MIME type, which is application/octet-stream).

The Element class provides getter methods to extract information from an Element object. There are four getter methods. The getName() method returns the element name. The getData() method returns the element's contents. The getNamespace() method returns the element namespace (if any, otherwise an empty string). The getMimeType() method returns the element's MIME type.

The Message class

The Message class represents a complete JXME message. To create a JXME message, I first create Element objects corresponding to the message's total individual elements. I can then put the Element objects in an array and pass the array to the Message class's constructor.

Message message = new Message (elementsArray);

The Message class contains three getter methods that help extract individual Element objects in the JXME message:

  1. The getElementCount() method returns an integer that represents the number of elements in the Message object.
  2. The getElement() method takes an integer, which represents an index. The method returns the Element object at that specific index.
  3. The getSize() method returns an integer. The integer value represents the size of the JXME message in bytes.

The PeerNetwork class

The PeerNetwork class contains the methods to allow JXME communication with the relay. The PeerNetwork class is like a communication module that internally uses different Message and Element objects and handles all communication with the relay.

Naturally, you would expect the PeerNetwork class to have methods to perform the various tasks managed previously between the relay and the mobile client. The PeerNetwork class manages all the messaging and also manages other support tasks, such as maintaining the identity of various messages exchanged between the relay and the client.

In short, PeerNetwork is the most important class that you can use as the JXME client-side implementation to integrate a J2ME device into a JXTA network. In Part 2, I will use this class to integrate a J2ME client into a JMS network using the JXTA network. Following is a discussion on the various methods of the PeerNetwork class.

The createInstance() method

You can instantiate the PeerNetwork class by using one of the two overloaded static factory methods called createInstance(). The constructors of the PeerNetwork class are private, so you cannot use them for instantiation.

One of the two createInstance() methods takes only a single string parameter, which specifies the name you want for the JXME peer. The createInstance() method internally performs the following tasks:

  1. It calls the PeerNetwork class's constructor, which takes two parameters: the peer name and the name of the peer group that you want to join. As there was just one parameter passed to the createInstance() method (which specifies the peer name), I don't have anything to specify for the peer group name. Therefore, the createInstance() method uses the default group name (NetPeerGroup). This means that if you don't want to join any peer group, you will automatically join the NetPeerGroup peer group.
  2. The constructor internally reduces the size the group identifier (also called trimming). This helps in optimizing outgoing data traveling over the wireless network.
  3. Next, the createInstance() method instantiates an HttpMessenger object. The HTTPMessenger class is part of the JXME implementation. It is a helper class, which other methods of the PeerNetwork class use to communicate with the relay. Apparently, the only purpose of introducing this class is to provide a layer of abstraction between the higher-level JXME classes (such as the PeerNetwork class) and lower-level J2ME/MIDP classes.

    For example, JXME comes with two different versions of the HTTPMessenger class, one for CLDC and the other for CDC devices. This means that by providing the HTTPMessenger class, JXME designers have made the higher-level JXME classes work on top of different flavors of J2ME.

The two-argument PeerNetwork.createInstance() method is different from the one-argument method in only one way; it takes the peer group identifier as the second argument. A call to the two-argument PeerNetwork.createInstance() method results in joining the specified peer group (instead of the default NetPeerGroup group in the case of the single argument PeerNetwork.createInstance() method).

The connect() method

After instantiating the PeerNetwork object, you call its connect() method to establish connection with the relay. You pass two parameters to the connect() method, as shown in Listing 16:


Listing 16. PeerNetwork.connect()
//PeerGroupDemo.java
byte[] persistentState = 
peerNetwork.connect("http://172.16.0.37:1900",null);

The first parameter is a string that contains the relay URL. This is the IP address and port number where the relay is executing. You must know the address of the relay in order to connect to it.

The second argument is the peer identifier that wants to connect to the relay. But, I don't know the peer identifier yet, so I want to request the relay to issue me a peer identifier. That's why I have passed null as the second parameter to the connect() method call.

The connect() method carries out the following steps:

  1. The connect() method internally calls the connect() method of the HttpMessenger class and passes both arguments to it.
  2. The HttpMessenger.connect() method constructs the URL (shown in Listing 2) based on the information passed to it.
  3. Next, the HttpMessenger.connect() method authors the peer identifier request and sends the request to the relay.
  4. If the relay successfully returns a peer identifier, the HttpMessenger.connect() method sends the connection establishment request to the relay.
  5. After a successful connection, the PeerNetwork.connect() method returns the peer identifier in the form of a byte array. This peer identifier represents the JXME client in this communication session. The JXME client uses this identifier for subsequent communication with the relay as well as in any future sessions. Therefore, I need to save this identifier for subsequent use.

If you supply a peer identifier as the second parameter to the PeerNetwork.connect() method, the method's internal workings are slightly changed. Now the method does not need to issue a peer identifier request, so it skips this part and directly issues a connection establishment request.

Note that when you pass null as the second parameter value to the PeerNetwork.connect() method, you are assigned a new peer identifier. Next time, you should use this peer identifier for connecting to the relay. Why? Because if you keep on passing null as the second parameter value, you get a new identifier every time. This is like creating a new mailbox every time you want to check your mail. You won't be able to retrieve your incoming mail.

The create() method

You use the create() method to create a JXTA resource (for example, a pipe or a peer group). The method takes four parameters:

  1. The type of resource to be created. If you want to create a pipe, you pass PeerNetwork.PIPE as the first parameter's value. Similarly, if you want to create a peer group, you pass PeerNetwork.GROUP.
  2. The name you want to give to the resource. For example, if you are creating a pipe that you want to name myPipe, you pass on "myPipe" as the second parameter's value.
  3. A predefined identifier for the resource. You can pass null as the third parameter's value, if you want to let the relay provide the identifier.
  4. An additional argument specifying the pipe's type. For example, if you are creating a unicast pipe, you pass PeerNetwork.UNICAST_PIPE as this field's value. If you are creating a propagate pipe, you pass on PeerNetwork.PROPAGATE_PIPE as this field's value. Note that you pass null as this parameter's value if you are creating a peer group.

The create() method authors a resource creation message. It then hands the message over to a private helper method named sendMessage(), which adds the message to the queue of outbound messages.

The method returns an integer that the application can use to match responses from the relay. This mechanism is needed because the responses from the relay might arrive in random order.

The join() method

You can use the PeerNetwork.join() method to join a particular peer group. This method takes two parameters, the identifier of the peer group that you want to join and an authentication password. The current JXME implementation does not support using passwords and ignores them if one is provided.

The join() method simply authors the join request message and places it in a message queue. The message queue contains all the messages (for example, a peer group joining request, pipe creation request, or a search request) that you might have asked the PeerNetwork class to send to the relay. The queue's outbound messages are sent to the relay when you call the poll() method. I will describe how the PeerNetwork.poll() method operates further along in the article.

The poll() method

You can call the PeerNetwork.poll() method to poll the relay for incoming messages (I discussed the polling mechanism in Retrieving the Response Messages). The following code shows how a JXME client application polls for incoming messages.

Message message = peerNetwork.poll (2000);

The poll() method takes just one parameter, a time out value. This value specifies the time (in milliseconds) that the application wants to wait for a message to arrive. A value of 0 indicates to wait forever until a message arrives.

When a JXME client (a JXME peer) polls the relay, the PeerNetwork.poll() method first examines its queue of unsent outbound messages. If the method finds an unsent message in the queue, it places the message in the request's HTTP body part and sends it to the relay using the HTTP POST method.

Naturally, I expect the relay to send me some incoming message in response to polling. The relay might not have incoming messages for the JXME client. If it doesn't, the poll() method returns null.

If the relay has a message for me, it responds with the response message. The poll() method internally parses the response, creates a Message object corresponding to the incoming message, and returns the Message object to the calling application. Naturally, the calling application would like to call various methods of the Message object to extract information from it. I will provide an example of this later in Listing 20.

The send() method

You use the send() method to send your messages to specific pipes. The method takes two parameters: the message to be sent and the target pipe's identifier.

The send() method internally passes the parameters to a private method of the PeerNetwork class called pipeOperation(). The pipeOperation() method is a helper method that provides common functions used by all the pipe-based methods, such as listen() and send().

Recall that while discussing Listing 14, I mentioned a few application-specific elements (sender, message, and fileReference) that form a customized message. The pipeOperation() method takes the customized message passed to it and adds a few more elements to it (like the target pipe identifier, the request Id, and destination address elements in Listing 14).

The send() method finally places the message in the queue for dispatch to the relay.

The search() method

A J2ME application uses the PeerNetwork.search() method to search different JXTA resources (like peer groups and pipes). The search() method takes four parameters:

  1. The type of the resource to search for. For example, PeerNetwork.GROUP, if you want to search for a peer group or PeerNetwork.PIPE, if you want to search for a pipe.
  2. The name of the resource attribute against which the search query is matched. For example, "Name" if you want to search for a resource by specifying its name.
  3. A string specifying the search query. For example, "myPipeName" in case you are searching for a pipe named myPipeName.
  4. A threshold value specifying the maximum number of responses a remote peer can send

The search() method authors the search request according to the data provided as parameters. Recall that I discussed the search messaging between the J2ME client and the relay in Listings 8 and 9.

The search() method then hands over the authored search query to the sendMessage() method, which places the messages in the queue of outbound messages. The search() method returns an integer value, which specifies the search request message. You use this identifier to match responses coming from the relay.

The listen() method

You use the listen() method for opening the pipe for input. The listen() method takes just one parameter, the pipe's identifier. The PeerNetwork.listen() method internally uses the pipeOperation() method for request authoring and then passes the message to the sendMessage() method. The sendMessage() method places the final message in the queue of unsent messages. The listen() method returns an integer identifier to be used for matching the responses from the relay.


A JXME tutorial

You've seen the major classes in the current JXME implementation. I'll now present a few programming examples to demonstrate how you can use these classes to do some of the tasks that you commonly encounter while developing JXME applications, including:

  • How to connect to the relay
  • How to create a peer group
  • How to search for a peer group
  • How to process JXME messages
  • How to send and receive messages over pipes

I'll explain these tasks in a step-by-step manner, and this demonstration provides the basic ground for developing the set of classes that you will develop in Part 2 of the series in order to integrate J2ME clients into JMS applications.

You will develop two sample MIDlet applications (PeerGroupDemo and PipeDemo) to demonstrate client-side JXME application development. These MIDlets put the pieces together and show you how to use the JXME classes and methods I have already introduced. The PeerGroupDemo sample MIDlet demonstrates all peer group-related tasks and the PipeDemo MIDlet shows how to create, search, and use pipes.

The code listings that accompany the demonstrations only show the code specific to the demonstration and not the complete MIDlet application. However this article's source code download contains complete source code as well as compiled versions of both sample MIDlets. It also contains instructions in a readme file to show how to use the sample MIDlets.

How to connect to the relay

Both sample MIDlets need to connect to the relay before doing anything else. Therefore, I have written relay connection code in the constructors of both MIDlets. Listing 17 demonstrates the steps a JXME client has to perform in order to join the JXTA network through the JXTA relay.


Listing 17. How to connect to the relay
// PeerGroupDemoMIDlet.java
// peerNetwork is an object of the PeerNetwork class.
try {
    peerNetwork = PeerNetwork.createInstance ("JxmeTestPeer");
    byte[] persistentState = peerNetwork.connect
                             ("http://localhost:9700", null); }
}
catch (IOException ioEx) {
ioEx.printStackTrace ();
}

Listing 17 shows the following steps:

  1. Start up JXTA on the JXME client by creating an instance of the PeerNetwork class. To do this, use the single parameter PeerNetwork.createInstance() method and only specify the peer name.
  2. Connect to the JXTA relay at the specified URL using the PeerNetwork.connect() method.

As a result of a successful connect request, the JXME peer joins NetPeerGroup. If, later on, a peer wants to join another peer group, it searches the peer group of interest and then joins the group.

Working with peer groups

The PeerGroupDemo MIDlet demonstrates the use of peer groups. This MIDlet contains three methods: createPeerGroup, searchPeerGroup, and joinPeerGroup, which demonstrate how to create, search, and join peer groups, respectively. Another method named processMessage() demonstrates how to process an incoming JXME message and extract useful information from it.

The PeerGroupDemo constructor calls these methods in a sequence just for the sake of simple demonstration. First, it creates a peer group, then searches for that group, and finally joins the group.

How to create a peer group

Listing 18 shows a method named createPeerGroup, which demonstrates the steps you need to follow in order to create a peer group. You will find this method in the PeerGroupDemo MIDlet.


Listing 18. How to create a peer group
 
//PeerGroupDemo.java
public String createPeerGroup(String groupName)
{
    String newGroupId = null;
    try {
        /***Step 1***/
        int messageID = peerNetwork.create ( 
                                PeerNetwork.GROUP, 
                                groupName, 
                                null, 
                                null);
        /***Step 2***/
        Message msg = peerNetwork.poll (5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        newGroupId = processMessage(msg, messageID, "success");
    }
    catch (IOException ie) {

        ie.printStackTrace ();
    }
    return newGroupId;
}

The createPeerGroup() method takes a string type parameter, which is the name of the peer group that you want to create. Listing 18 demonstrates that in order to create a peer group, you have to make a call to the PeerNetwork.create() method. You pass a string constant PeerNetwork.GROUP as the first parameter to the create() method call and the name of the peer group as the second parameter. If you want to specify the identifier for the peer group you are creating, you can pass the identifier as the third parameter. If you want to let the relay decide the identifier, you can pass null as the third parameter. You always pass null as the fourth parameter while creating peer groups, as this parameter is not needed for peer group creation.

You store the request identifier returned by the create() method for future use to match the response to this request.

The next step is to call the poll() method of the PeerNetwork object. The poll() method sends the create peer group request from the queue to the relay and also checks if any incoming messages for the client are available. In case the relay has a message for you, the poll() method returns a Message object; otherwise, it returns null.

If the Message object is not null, you need to check if the message is in response to the create group request or a response to some other message. For this purpose, you need to process and check the Message object. For this, I have written a method named processMessage().

How to process JXME messages

Listing 19 presents the processMessage() method, which demonstrates a simple processing logic for a JXME message. I have written the processMessage() method to demonstrate the use of various methods of the Message and Element classes.

The processMessage() method takes three parameters: a Message object named msg, an integer type request identifier named reqId, and a string named successCriterion.

The processMessage() method checks whether the Message object is in response to the request whose identifier is passed as the second parameter. If it is, the processMessage() method probes the Message object further to determine if the request (for example, a peer group search request) was successfully met.

The success criterion is different for different types of requests. That's why I have passed the third parameter named successCriterion. The processMessage() method checks whether the success criterion string matches with the contents of the message's response element. If it matches, the processMessage() method extracts the contents of an element named id and returns the contents.The contents of the id element contain the identifier of the searched resource (for example, a peer group).

You use the processMessage() method to process the responses to create, search, join, listen, and send requests. For these requests, the responses do not return any resource identifier. In this case, the processMessage() method returns the contents of the response element instead of the id element.


Listing 19. How to process JXME messages
//PeerGroupDemo.java
private String processMessage (Message msg, int reqId, String 
successCriterion)
{
    int requestIdentifierInResponse = 0;
    String searchedResourceIdentifier = null;
    String responseString = null;

    try
    {
        /***Step 1***/
        for (int j=0; j<msg.getElementCount(); j++) {
            /***Step 2***/
            Element e = msg.getElement(j);
            /***Step 3***/
            if ((e.getNameSpace()).equals("proxy")) {
                if ((e.getName()).equals("requestId"))
                    requestIdentifierInResponse = Integer.parseInt(
                                                   new 
String(e.getData()));
                else if ((e.getName()).equals("response"))
                    responseString = new String (e.getData());
                else if ((e.getName()).equals("id"))
                    searchedResourceIdentifier = new String 
(e.getData());
            }
        } //for (int j=0;) 

        /***Step 4***/
        if (requestIdentifierInResponse == reqId) {
            /***Step 5***/
            if (responseString.equals (successCriterion)) {
                if (searchedResourceIdentifier != null)
                    /***Step 6***/
                    return searchedResourceIdentifier;		 
                else
                    /***Step 7***/
                    return responseString;
            }
        }//if (requestIdentifierInResponse == reqId)

    }
    catch(NumberFormatException ne){
        ne.printStackTrace();
    }
    return null;
}

Listing 19 demonstrates the following steps:

  1. First, call the getElementCount() method of the Message object, which returns the number of elements in the message.
  2. Now loop through each element by calling the getElement() method one by one. The getElement() method takes an integer parameter (a zero-based index value) and returns the Element corresponding to the index.
  3. Next, process the Element in one or more if statements. In Listing 19, I've used the Element class's getName(), getNamespace(), and getData() methods to extract the data from three particular message elements (requestId, response, and id). All three elements belong to the proxy namespace. I stored the requestId element's contents in a variable named requestIdentifierInResponse, the response element's contents in a variable named responseString, and the id element's contents in a variable named searchedResourceIdentifier. The requestIdentifierInResponse variable now holds the identifier of the request to which the msg object is a response. The searchedResourceIdentifier variable holds the identifier of the JXTA resource (for example, a peer group or a pipe) that someone has returned in response to the search query. The responseString variable holds the string that carries information whether the request was successfully met or failed.
  4. The requestId element specifies the request to which this Message object is a response. Therefore, I have to match the value of the requestIdentifierInResponse variable with the reqId parameter. If they don't match, I don't need to process any further.
  5. If the msg object is really in response to the request identified by the reqId identifier, I match the success criterion with the contents of the element named response.
  6. If the success criterion matches, I simply return the contents of the id element. This element always contains the identifier of the resource searched in all responses to search queries.
  7. Finally, if the success criterion matches, but the id element is not present (which means the searchedResourceIdentifier variable is null), I return the response element's contents. This step ensures that you can use the processResponse() method to process responses to join, listen, and send requests that do not contain the id element.

You can use this example to implement message processing logic according to your own requirements. In Part 2 of the series, I will use these concepts to implement J2ME client-side messaging requirements.

How to search for a peer group

Now, I will demonstrate how you can search for a specific peer group. Listing 20 shows a method named searchPeerGroup(), which I have written to demonstrate how you can search for peer groups. This method is included in the PeerGroupDemo MIDlet.


Listing 20. How to search for a peer group
//PeerGroupDemo.java
public String searchPeerGroup(String groupName)
{

    String newGroupId = null;
    try 
    {
        /***Step 1***/
        int messageID = peerNetwork.search (
                                   PeerNetwork.GROUP, 
                                   "Name", 
                                   groupName, 
                                   1);
        /***Step 2***/
        Message msg = peerNetwork.poll (5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        newGroupId = processMessage(msg, messageID, "result");

    }
    catch (IOException ie) {
        ie.printStackTrace ();
    }
    return newGroupId;
}

The searchPeerGroup() method takes just one parameter, which is the name of the peer group you want to search. The method demonstrates the following steps:

  1. First, call the PeerNetwork class's search() method. You pass on the four parameters to the search() method call. As a result of this method call, the search message is authored and placed in the queue of outbound messages inside the PeerNetwork object. The search() method returns an identifier for the search message. In Listing 20, I have stored the identifier in an integer variable named messageID.
  2. Now you can call the poll() method of the PeerNetwork class, which returns a Message object.
  3. The next step is to check whether the object returned by the poll() method is null. If it isn't, you have received a message from the relay.
  4. Call the processMessage() method to determine if the message is in response to the search query you just sent. If it is, the processMessage() method returns the identifier of the peer group you were searching.

How to join a peer group

Listing 21 shows a simple method named joinPeerGroup to demonstrate how you join the peer group. The joinPeerGroup() method takes just one parameter, the identifier of the peer group that you want to join.


Listing 21. How to join a peer group
//PeerGroupDemo.java
public String joinPeerGroup(String groupId)
{
    String joinOk = null;
    try 
    {
        /***Step 1***/
        int messageID = peerNetwork.join (groupId, null);

        /***Step 2***/
        Message msg = peerNetwork.poll (5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        joinOk = processMessage(msg, messageID, "success");
    }
    catch (IOException ie) {
        ie.printStackTrace ();
    }
    return joinOk;
}

Listing 21 shows that joining a peer group simply requires you to call the PeerNetwork class's join() method. The join() method takes as parameter the group's identifier.

After calling the join() method, you poll the relay and then call the processResponse() method (already discussed in Listing 19) to check if you have joined the peer group.

After you have joined the group, you might also want to instantiate another PeerNetwork object using the two-argument constructor. I advise you to instantiate a new PeerNetwork object for every peer group you join. This way, the communication logic in your application remains straightforward and simple to understand.

Working with pipes

This section demonstrates how JXTA pipes work. As you know, pipes are used for peer-to-peer communication, which involves sending and receiving messages over pipes. You have written a simple MIDlet application named PipeDemo to demonstrate pipe communication. You'll find the complete MIDlet in the source code for this article.

Listing 22 shows the PipeDemo MIDlet's constructor.


Listing 22. The PipeDemo constructor
//PipeDemo.java
public PipeDemo()
{
    try {
        peerNetwork = PeerNetwork.createInstance ("JxmeTestPeer");
        byte[] persistentState = peerNetwork.connect(
                                     "http://localhost:9700", 
                                      null); 
        String pipeId = createPipe("JxmeTestPipe");
        String listenOk = listenToPipe(pipeId);
        String searchedPipeId = searchPipe("JxmeTestPipe");
        Message msg = authorMessage("hello buddy!");
        String sendOk = sendMessage (msg, searchedPipeId);
    }		 
    catch (IOException ie) {
        ie.printStackTrace ();
    }
}


As you can see from Listing 22, the PipeDemo constructor first instantiates the PeerNetwork object and then calls its connect() method. I have already discussed these points.

The constructor then calls five methods: createPipe(), listenToPipe(), searchPipe(), authorMessage(), and sendMessage(). You have written these five methods to enable readers to easily understand how to perform the following tasks:

  • How to create a pipe
  • How to open a pipe for listening
  • How to search for a pipe
  • How to author a message
  • How to send a message to a pipe

The final outcome of these method calls in the constructor is that you create your own pipe and start listening to it. Then you search the same pipe, author a message, and then send the message to your own pipe. Naturally, you receive the message you just sent.

This arrangement provides you with a single MIDlet that demonstrates all the major operations you need to do on pipes. You can use the basic concepts to build your own application logic. In Part 2 of the series, I will use these concepts to integrate a J2ME client into a JMS application.

How to create a pipe

Listing 23 shows the createPipe() method implementation and demonstrates how to create a pipe.


Listing 23. How to create a pipe
//PipeDemo.java
public String createPipe(String pipeName)
{
    String pipeId = null;		 
    try 
    {
        /***Step 1***/
        int messageID = peerNetwork.create (
                                  PeerNetwork.PIPE,
                                  pipeName, 
                                  null, 
                                  PeerNetwork.UNICAST_PIPE);

        /***Step 2***/
        Message  msg = peerNetwork.poll(5000);

        /***Step 3***/
        if (msg == null)
            return null;
            
        /***Step 4***/
        pipeId = processMessage(msg, messageID, "success");

        } 
        catch (IOException ie) {
            ie.printStackTrace ();
        }
        return pipeId;
    }//createPipe()


You can see from the createPipe() method that creating a pipe is very similar to creating a peer group. You just have to call the PeerNetwork class's create() method. When you called the create() method in Listing 18 to create a peer group, you passed PeerNetwork.GROUP as the first parameter's value. Now, you pass PeerNetwork.PIPE as the first parameter's value.

Also note that when you created a peer group, you didn't need to specify the last parameter to the create() method, so you passed null. When you create a pipe, you also need to pass the last parameter, which specifies the pipe type. If you are creating a unicast pipe, you pass on PeerNetwork.UNICAST_PIPE. If you are creating a propagate pipe, you pass on PeerNetwork.PROPAGATE_PIPE. The create() method returns the identifier of the create pipe request message.

After calling the create() method, you call the poll() method, which returns a Message object. You pass the Message object as well as the create pipe request identifier to the processMessage() method that I discussed in Listing 18. The processMessage() method tells us the pipe identifier.

A successful pipe creation does not start receiving incoming messages until you open it and listen for messages. The next example demonstrates opening a pipe and listening over it.

How to open a pipe for listening

Notice the listenToPipe method shown in Listing 24.


Listing 24. How to open a pipe for listening
//PipeDemo.java
public String listenToPipe(String pipeId)
{
    String listenOk = null;
    try 
    {
        /***Step 1***/
        int messageID = peerNetwork.listen (pipeId);

        /***Step 2***/
        Message msg = peerNetwork.poll(5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        listenOk = processMessage(msg, messageID, "success");

        if (listenOk.equals("success"))
            System.out.println (">>> listening over pipe  .. ");
        } 
    catch (IOException ie) {
        ie.printStackTrace ();
    }

     return listenOk;

}

To open a pipe for listening, you need to call the listen() method of the PeerNetwork class, passing the pipe's identifier. The listen() method returns the identifier of the listen request message it authored.

After calling the listen() method, you call the poll() method. The poll() method returns a Message object, which you pass on to the processMessage() method along with the listen request message identifier. If the processMessage() method does not return null, it means your listen request was successful and the pipe has been opened for input.

How to search for a pipe

If you want to send a message to a peer, you need to know the identifier of the peer's pipe. Therefore, you first search for a pipe. After you know the pipe identifier, you can directly send a message over the pipe to the peer, as shown in the searchPipe() method of Listing 25.


Listing 25. How to search for a pipe
//PipeDemo.java
public String searchPipe(String pipeName)
{
    String pipeId = null;		 
    try
    {
        /***Step 1***/
        int messageID = peerNetwork.search (
                              PeerNetwork.PIPE, 
                              "Name", 
                              "JxmeTestPipe", 
                              1);

        /***Step 2***/
        Message  msg = peerNetwork.poll(5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        pipeId = processMessage(msg, messageID, "result");
    }		 		 		 
    catch (IOException ie) { 
        ie.printStackTrace();
    }
    return pipeId;
} 

The searchPipe() method takes just one parameter, the pipe's name. It calls the search() method of the PeerNetwork class, passing the four parameters mentioned earlier. Notice in Listing 25 that you have passed on PeerNetwork.PIPE as the first parameter's value to the search() method. This means you are searching for a pipe.

The search() method returns the identifier of the search query message. You then call the poll() method, which returns a Message object. You pass on the search query identifier and the Message object to the processMessage() method, which checks if the Message object is a response to the search query and extracts the pipe identifier from the Message object.

How to author a JXME message

I have already mentioned that you use the Element and Message classes to author your JXME messages. I have written a simple method in Listing 26 named authorMessage(), which demonstrates this process.


Listing 26. How to author a JXME message
//PipeDemo.java
public Message authorMessage (String messageString)
{
    /***Step 1***/		 		 		 
    Element[] elements = new Element [1];

    /***Step 2***/		 		 		 
    elements [0] = new Element("message", 
                                messageString.getBytes(), 
                                null, 
                                null);
            
    /***Step 3***/		 		 		 
    Message message = new Message (elements);
    
    return message;

}

The authorMessage() method takes a string parameter named messageString, which represents the message contents that you want to send over the pipe. In Listing 26, you have performed the following steps:

  1. Authored an array of elements.
  2. Instantiated an Element object. Notice that you pass on a string (for example, "message") as the first parameter. This string specifies the name of the element, which wraps the actual contents of the message. The second parameter specifies the message contents (the messageString parameter), which should be supplied as a byte array (for example, messageString.getBytes()). The third element specifies the element namespace, which is null if you want to create an element with no namespace. The last parameter specifies the message's MIME type (null if you want to use the default MIME type).
  3. Used the array of step 1 (which contains just one Element object) to construct the Message object.

The authorMessage() method returns the Message object, which you now send over the pipe.

How to send a message over a pipe

Listing 27 shows how you can send a message over a pipe using the sendMessage() method.


Listing 27. How to send a message over a pipe
//PipeDemo.java
public String sendMessage(Message message, String pipeId)
{
    String sentOk = null;
    try
    {
        /***Step 1***/
        int messageID = peerNetwork.send (pipeId, message);

        /***Step 2***/

        Message  msg = peerNetwork.poll(5000);

        /***Step 3***/
        if (msg == null)
            return null;

        /***Step 4***/
        sentOk = processMessage(msg, messageID, "success");
    }
    catch (IOException ie){
        ie.printStackTrace();
    }           
    return sentOk;
}

The sendMessage() method takes a Message object and a pipe identifier and sends the message over the pipe. The sendMessage() method calls the send() method of the PeerNetwork class, passing the Message object and the pipe identifier along with the method call. This sends the message over the pipe.

You can now call the poll() method to get the message you just sent over the pipe.

Conclusion

You now know why you cannot directly integrate J2ME clients into a JMS network. I've discussed messaging between a JXME client and a JXTA relay, and demonstrated the programmatic steps required in a J2ME MIDlet to communicate with a JXTA relay. The scene is now set to start developing a set of classes that use JXTA to integrate J2ME clients in a JMS application. This is called JXTA4JMS, which I will develop in Part 2 of this series. I will use the concepts presented in this article to build wireless connectivity for JMS clients.


Resources

  • Download the source code of this article.

  • For more information on JMS, see the JMS page at the Sun Microsystems Web site. Also, read this comprehensive tutorial on JMS for and introduction of the JMS API.

  • Get more information on JNDI at the Sun Microsystems Web site.

  • Visit the JXTA home page for information on Project JXTA objectives.

  • Read Mobile P2P messaging, Part 2 (developerWorks, January 2003), which explains one of the samples that comes with the JXME download.

  • Take the tutorial Introducing the Java Message Service (developerWorks, June 2004) to learn the basic programming techniques for creating JMS programs.

About the author

Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=31908
ArticleTitle=Wireless messaging with JXTA, Part 1: Using JXTA technology
publish-date=11162004
author1-email=fkhan872@yahoo.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).