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 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:
- The administrator creates a queue or a topic destination object on the provider.
- A sender client looks up the destination object of his choice.
- The sender writes a JMS message.
- The sender sends the message to the destination object.
Figure 2. 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.
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
QueueConnectionFactoryandTopicConnectionFactoryclasses in the J2ME device. This lets you createQueueConnectionandTopicConnectionobjects using their respective connection factories. - The ability to create and maintain
QueueandTopicobjects 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.
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.
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:
- A JXME client sends a request to a relay to create a pipe.
- 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).
- The relay returns the newly created pipe's identifier to the JXME client.
Figure 3. 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

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:
-
All JXME messages start with the string
jxmg. This is shown as the message signature field in Figure 4's header. -
The
0afterjxmgspecifies the JXME message's version number. - The header's third field is a two-byte value, which is
01in 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. -
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 (
05in 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 (
proxyin Listing 1) is automatically assigned an identifier2. All subsequent entries get an identifier after an increment of 1 to the previous identifier. For example, if I create another namespace, sayourNamespace, it would have an identifier3.The identifiers
0and1are preassigned to an empty namespace andjxtanamespace 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.
-
The last two bytes
07specify 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:
-
Each element starts with a signature
jxel, which indicates the start of a message element. -
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 theproxynamespace (recall the header's fourth field, where I defined2as the identifier forproxynamespace). The namespace identifier1in Listing 1's last two elements specifies the predefined namespacejxta. -
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
0for this field means that I will use default values for the optional fields (the default MIME type isapplication/octet-streamand the default content encoding isbase64). -
The element's name follows the flag byte. The name actually consists of
two subfields; first is a two-byte value (
07in the first element) that specifies the number of characters in the element name. The length value is followed by the element name itself (requestin the first element). -
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 (
0006in the first element) and the content itself (createin 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

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.
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:
unknown-unknown-- This specifies that the requestor doesn't have a peer identifier.0-- This is the default time-out value in milliseconds. A value of 0 means that the client waits until it receives a response.-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.http:// 172.16.0.37:2481-- This is the IP address and the port number where the relay is listening.EndpointService:jxta-NetGroup-- This part is an identifier for the relay. This value is always the same in all JXME requests for peer identifiers.uuid-DEADBEEFDEAFBABAFEEDBABE0000000F05-- This is a class identifier for the relay service. JXTA has defined class identifiers for different types of JXTA services.pid-- The last part specifies the actual command string. Herepidmeans 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:
- The first element specifies that it is a response to a peer identifier request.
- The second element specifies the peer identifier assigned to the peer.
- The third element tells the endpoint destination address. In this case, the destination is the JXME client.
- The last element specifies the endpoint source address. In this case, the source is the relay.
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:
- The URL starts with a valid peer identifier (the same identifier that you received in response to the peer identifier request).
-
You use a different command string,
connect, to request a connection. In the request for a peer identifier the command string waspid. -
The value after the command string
3600000tells 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
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

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:
-
The
requestelement specifies the request's purpose (joining a peer group). -
The
idelement holds the peer group identifier. -
The
argelement 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. -
The
requestIdelement 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. -
The
EndpointDestinationAddressandEndpointSourceAddresselements, 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.
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

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:
-
The
requestelement specifies that it is a search request. -
The
typeelement specifies the type of the resource that is searched. The contents of thetypeelement in Listing 8 includeGROUP, which means you are searching for a peer group. Other values of this field might bePIPE(if you are searching for a pipe) andPEER(if you are searching for a peer). -
The
attrelement specifies the attribute of the searched resource. For example, if you are searching for a particular peer group by name, the content of theattrelement isname, as shown in the listing above. -
The
valueelement 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 thevalueelement. You can also use the wildcard character (asterisk, *) in the query string to create pattern-matching queries. -
Different JXTA peers are expected to send messages in response to the search query. The
thresholdelement 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.
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

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:
- The first element specifies that it is a request to create a resource.
- The second element tells the name of that resource.
- The
requestIdis an integer associated with the request, which the request author uses to match responses from the relay. -
The
typeelement specifies the resource's type (pipe, in this case). If you were creating a peer group, the value of this field would have beenGROUP. - The
argelement specifies the pipe's type. For JXME clients, this is eitherUnicastorJxtaPropagate. - 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:
-
The first element tells you that this is a
responsemessage and its content indicates that the request was completed successfully. - The
idelement 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.
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

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 |
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

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.
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
Elementclass - The
Messageclass - The
PeerNetworkclass
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 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:
-
The
getElementCount()method returns an integer that represents the number of elements in theMessageobject. -
The
getElement()method takes an integer, which represents an index. The method returns theElementobject at that specific index. -
The
getSize()method returns an integer. The integer value represents the size of the JXME message in bytes.
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:
-
It calls the
PeerNetworkclass'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 thecreateInstance()method (which specifies the peer name), I don't have anything to specify for the peer group name. Therefore, thecreateInstance()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. - The constructor internally reduces the size the group identifier (also called trimming). This helps in optimizing outgoing data traveling over the wireless network.
-
Next, the
createInstance()method instantiates anHttpMessengerobject. TheHTTPMessengerclass is part of the JXME implementation. It is a helper class, which other methods of thePeerNetworkclass 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 thePeerNetworkclass) and lower-level J2ME/MIDP classes.For example, JXME comes with two different versions of the
HTTPMessengerclass, one for CLDC and the other for CDC devices. This means that by providing theHTTPMessengerclass, 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:
- The
connect()method internally calls theconnect()method of theHttpMessengerclass and passes both arguments to it. -
The
HttpMessenger.connect()method constructs the URL (shown in Listing 2) based on the information passed to it. -
Next, the
HttpMessenger.connect()method authors the peer identifier request and sends the request to the relay. - If the relay successfully returns a peer identifier, the
HttpMessenger.connect()method sends the connection establishment request to the relay. -
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:
- The type of resource to be created. If you want to create a
pipe, you pass
PeerNetwork.PIPEas the first parameter's value. Similarly, if you want to create a peer group, you passPeerNetwork.GROUP. - 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.
- A predefined identifier for the resource. You can pass
nullas the third parameter's value, if you want to let the relay provide the identifier. - An additional argument specifying the pipe's
type. For example, if you are creating a unicast pipe, you passPeerNetwork.UNICAST_PIPEas this field's value. If you are creating a propagate pipe, you pass onPeerNetwork.PROPAGATE_PIPEas this field's value. Note that you passnullas 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:
-
The type of the resource to search for. For example,
PeerNetwork.GROUP, if you want to search for a peer group orPeerNetwork.PIPE, if you want to search for a pipe. - 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.
-
A string specifying the search query. For example, "myPipeName" in case
you are searching for a pipe named
myPipeName. - 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.
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.
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:
-
Start up JXTA on the JXME client by creating an instance of the
PeerNetworkclass. To do this, use the single parameterPeerNetwork.createInstance()method and only specify the peer name. -
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.
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.
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().
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:
-
First, call the
getElementCount()method of theMessageobject, which returns the number of elements in the message. - Now loop through each element by calling the
getElement()method one by one. ThegetElement()method takes an integer parameter (a zero-based index value) and returns theElementcorresponding to the index. - Next, process the
Elementin one or moreifstatements. In Listing 19, I've used theElementclass'sgetName(),getNamespace(), andgetData()methods to extract the data from three particular message elements (requestId,response, andid). All three elements belong to theproxynamespace. I stored therequestIdelement's contents in a variable namedrequestIdentifierInResponse, the response element's contents in a variable namedresponseString, and theidelement's contents in a variable namedsearchedResourceIdentifier. TherequestIdentifierInResponsevariable now holds the identifier of the request to which themsgobject is a response. ThesearchedResourceIdentifiervariable 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. TheresponseStringvariable holds the string that carries information whether the request was successfully met or failed. -
The
requestIdelement specifies the request to which thisMessageobject is a response. Therefore, I have to match the value of therequestIdentifierInResponsevariable with thereqIdparameter. If they don't match, I don't need to process any further. -
If the
msgobject is really in response to the request identified by thereqIdidentifier, I match the success criterion with the contents of the element namedresponse. -
If the success criterion matches, I simply return the contents of the
idelement. This element always contains the identifier of the resource searched in all responses to search queries. -
Finally, if the success criterion matches, but the
idelement is not present (which means thesearchedResourceIdentifiervariable isnull), I return the response element's contents. This step ensures that you can use theprocessResponse()method to process responses to join, listen, and send requests that do not contain theidelement.
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:
- First, call the
PeerNetworkclass'ssearch()method. You pass on the four parameters to thesearch()method call. As a result of this method call, the search message is authored and placed in the queue of outbound messages inside thePeerNetworkobject. Thesearch()method returns an identifier for the search message. In Listing 20, I have stored the identifier in an integer variable namedmessageID. -
Now you can call the
poll()method of thePeerNetworkclass, which returns aMessageobject. -
The next step is to check whether the object returned by the
poll()method isnull. If it isn't, you have received a message from the relay. -
Call the
processMessage()method to determine if the message is in response to the search query you just sent. If it is, theprocessMessage()method returns the identifier of the peer group you were searching.
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.
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.
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.
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.
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:
- Authored an array of elements.
- Instantiated an
Elementobject. 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 (themessageStringparameter), which should be supplied as a byte array (for example,messageString.getBytes()). The third element specifies the element namespace, which isnullif you want to create an element with no namespace. The last parameter specifies the message's MIME type (nullif you want to use the default MIME type). - Used the array of step 1 (which contains just one
Elementobject) to construct theMessageobject.
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.
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.
- 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.
Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.