In the first article of this series, you learned why Java 2 Platform, Micro Edition (J2ME)-enabled devices can't directly host client applications for enterprise messaging. That article also discussed how to use JXTA technology to integrate thin mobile clients in enterprise messaging solutions and demonstrated how to use JXTA protocols in a J2ME device.
This second article of the series shows you how to implement the bridge between a J2ME client and a JMS application using JXTA (JXTA-for-JMS, or JXTA4JMS for short). The JXTA4JMS implementation consists of two components: One runs inside a desktop PC (desktop-side JXTA4JMS); the other component runs inside a J2ME-based mobile device (mobile-side JXTA4JMS).
This article begins with a few use cases to show how a J2ME device or a JMS client uses JXTA4JMS. It then describes the JXTA4JMS architecture and explains the arrangement of classes in the JXTA4JMS implementation. Finally, the article demonstrates the actual implementation and provides a working application that integrates thin J2ME clients into Java Message Service (JMS) applications.
Consider a company that uses JMS for its enterprise-wide messaging requirements. The company has hosted a JMS provider, and its employees who use the JMS network act as messaging clients (message producers or consumers). The company wants the messaging clients to remain connected to each other. The JMS network fulfills this requirement as long as all users have access to their PCs.
JXTA4JMS is needed when users do not have access to their PCs (for example, when they are out of office). Suppose JMS user Alice leaves the office. She enables JXTA4JMS on her desktop and on her cell phone. The desktop-side JXTA4JMS starts listening for incoming JMS messages. Whenever it receives a JMS message for Alice, it forwards the message to Alice's cell phone. Similarly, when Alice wants to send a message to a colleague, she uses the mobile-side JXTA4JMS to send that JMS message. The recipient of the message receives the message as a normal JMS message. JXTA4JMS helps Alice to remain connected and continue messaging with the JMS network using her J2ME-enabled cell phone.
Notice the important feature of JXTA4JMS: It does not disturb the work of the other JMS users. If Alice no longer wants to use JXTA4JMS, she simply disables it. JXTA4JMS is transparent for other JMS users; they aren't affected and, therefore, do not know whether Alice enables or disables JXTA4JMS.
As you can see, JXTA4JMS handles two-way messaging between JMS clients and mobile clients. Let's consider these two use cases separately to clearly define the functionality of JXTA4JMS.
Messaging from a JMS client to a J2ME client
Suppose Bob wants to send a JMS message to Alice. Bob uses his desktop to send the message. Although Bob doesn't know this, Alice is not in her office at the time, but while leaving her office, she enables JXTA4JMS on her desktop and on her J2ME cell phone.
As soon as Alice enables JXTA4JMS on her desktop and cell phone, both sides prepare for a message exchange:
-
The desktop-side JXTA4JMS starts listening for JMS messages on Alice's queue on the JMS provider.
-
The mobile-side JXTA4JMS creates an input JXTA pipe and begins listening for incoming messages. The desktop-side JXTA4JMS searches for this pipe, treats it as an output pipe, and uses it to send messages to the mobile phone. Therefore, this pipe is used to send messages from the desktop to the mobile phone. Note that a JXTA pipe is a virtual communication channel with two ends (incoming and outgoing). The peer who creates the pipe sits at the incoming end, and the one who searches for it sits at the outgoing end.
- You need another JXTA pipe to send messages from the mobile to the desktop. The desktop-side JXTA4JMS creates this pipe and starts listening for incoming messages on the pipe from the mobile-side JXTA4JMS. The mobile-side JXTA4JMS searches for the pipe and uses it to send JMS messages to the desktop.
Now, two JXTA pipes exist: one for messages from desktop side to mobile side, and the other for mobile side to desktop side.
Now look what happens when Bob (a JMS user) sends a message to Alice (a JXTA4JMS user). Figure 1 shows a graphical view of the sequence of events.
Figure 1. Sending a message from a JMS client to a J2ME client

-
Bob sends a JMS message to Alice's queue on the JMS provider.
-
The JXTA4JMS implementation on Alice's PC constantly listens for messages on Alice's queue, so JXTA4JMS receives the JMS message from Bob.
-
JXTA4JMS now translates the JMS message into a JXTA format that can travel across the JXTA network. Alice's mobile client uses JXME to communicate with the JXTA network. Recall from the discussion in this series' first article that JXME clients don't talk directly to JXTA peers; they use relays to communicate with the JXTA network. However, this process is transparent to JXTA peers; they never know whether a particular message is routed through a relay or sent directly to the ultimate recipient. That's why the JXTA4JMS implementation authors a JXTA message (and not a JXME message, the structure of which was explained in the first article).
-
JXTA4JMS sends the message to the JXTA relay over the JXTA pipe the mobile-side JXTA4JMS creates. It is the JXTA network's responsibility to properly route the message over the output pipe and through the relay.
-
The JXTA relay automatically translates the message from a JXTA format to a JXME format before delivering the message to the mobile client.
-
The mobile-side JXTA4JMS implementation inside Alice's cell phone constantly listens for messages on the same pipe. So it receives Bob's message.
- The JXTA4JMS displays the message on Alice's cell phone screen.
Messaging from a J2ME client to a JMS client
Now, look at how JXTA4JMS helps Alice send a reply message to Bob. Figure 2 illustrates a graphical view of the scenario.
Figure 2. Sending a message from a J2ME client to a JMS client

The scenario in Figure 2 is similar to the one in Figure 1:
-
Alice writes her response message to Bob and asks the mobile-side JXTA4JMS implementation running on her cell phone to send it to Bob.
-
JXTA4JMS wraps Alice's message in the JXME message format discussed in the first article. This JXME message also wraps the name of the JMS message recipient (in this case, Bob).
-
JXTA4JMS sends the message to a JXTA relay. Note that this message is sent over the JXTA pipe that Alice's desktop-side JXTA4JMS creates.
-
The relay translates the JXME format into JXTA format.
-
The relay sends the JXTA message to Alice's desktop over the JXTA network, where her desktop-side JXTA4JMS is enabled.
-
The desktop-side JXTA4JMS receives the message from the relay. Note that the message is now in JXTA format.
-
The desktop-side JXTA4JMS translates the message to JMS format. It extracts the JMS client username Alice intends to message (Bob, in this case). It also searches for Bob's queue on the JMS provider.
-
The desktop-side JXTA4JMS sends the message to Bob's queue on the JMS provider.
- Bob's client-side JMS application listens on his queue. It receives the message from Alice.
Let's examine the architecture of the JXTA4JMS implementation. As you've already seen in the discussion of the JXTA4JMS usage model, the JXTA4JMS implementation consists of the desktop and mobile-side implementations. Take a look at both implementations.
The desktop-side implementation consists of several layers, as Figure 3 shows. A layered architecture ensures better reusability of JXTA4JMS. You can easily modify any of the JXTA4JMS layers according to your own requirements and still use the rest of the layers as such.
Figure 3. The desktop-side JXTA4JMS layers

Following are explanations of each JXTA4JMS layer:
-
The listener layer listens to JMS messages coming from the JMS network as well as JXTA messages coming from the J2ME device. The listener layer also establishes a JMS connection with the provider and creates pipes on the JXTA network to communicate with the J2ME mobile device. This layer consists of just one class named
Listener. -
The router layer has methods that can route JMS messages to the J2ME device (through the JXTA network), as well as J2ME messages to the JMS clients. This layer consists of one class named
Router. TheListenerclass listens to incoming messages and uses theRouterclass for appropriate routing of messages. -
The format conversion layer converts JMS messages to a JXTA format and vice versa. This layer consists of two classes:
JXTAToJMSMsgConverterandJMSToJXTAMsgConverter. TheRouterclass uses the format conversion classes to translate message formats before routing them to their destination. -
The networking layer handles the low-level details of working with both JMS and JXTA networks. This layer consists of classes
JXTASearchandJMSSearch. TheJXTASearchclass searches for a JXTA pipe the J2ME device creates and publishes over the JXTA network. This pipe is used to send messages to the J2ME device. TheJMSSearchclass searches for the JMS queue of the JMS user (recipient of the J2ME client's message). TheListenerandRouterclasses use these twoJXTASearchandJMSSearchclasses.
The mobile-side implementation of JXTA4JMS handles the wireless messaging (the sending and receiving of messages to and from the desktop-side JXTA4JMS implementation). A set of four classes, described next, works in a layered architecture to perform the messaging task.
-
The
JXMEMIDletclass is a sample MIDlet application I wrote to demonstrate the use of the JXTA4JMS messaging client with a simple user interface. -
The
JXTA4JMSMessagingClientclass is the most important class in the J2ME-side implementation. TheJXTA4JMSMessagingClientclass provides all low-level functionality such as connecting to a relay and exchanging and processing messages. This allows the higher-level application to focus only on user interface items. -
The
JXTA4JMSMessageclass authors and processes JXTA4JMS messages. A JXTA4JMS message is a JXME message customized for this application. TheJXTA4JMSMessagingClientuses this class to author and process JXTA4JMS messages. -
The
DataStoreclass is a low-level utility class that stores the identifier of the pipe used to receive messages from the JMS-side implementation. TheDataStoreclass handles all low-level details of J2ME record store use. TheJXTA4JMSMessagingClientclass uses this utility class to store and retrieve the pipe identifier.
Before you implement JXTA4JMS, you should first look at how to configure JXTA4JMS in a JXTA network. This helps you visualize how JXTA4JMS uses JXTA rendezvous peers and relays.
JXTA4JMS needs three JXTA instances running. The first instance represents the JMS user on the JXTA network (for example, Alice). This JXTA instance is part of the JXTA4JMS instance.
The second instance is a rendezvous peer. A rendezvous peer is a meeting place for all JXTA peers. It keeps JXTA advertisements cached with it, thus allowing JXTA peers to discover the presence of other peers in the network. I describe rendezvous peers later.
The third instance is a JXTA relay the mobile JXTA4JMS uses to communicate over the JXTA network.
The three JXTA instances' configuration requirements are different, which I will explain momentarily. In real-world applications, the three instances run on different machines connected to each other through some networking technology (for example, the Internet).
To see JXTA4JMS run on a single computer that might not be connected to any network, you can have the three instances running on the same machine. This JXTA4JMS implementation does not care whether the different instances run on the same machine or different machines. You can also configure the same JXTA instance to play two or all three roles.
I packaged this article's source code in the wi-jxta2source.zip file, where you will also find a readme file explaining how I tested JXTA4JMS.
When you run a JXTA instance for the first time, you see a JXTA configuration window. You need to fill in the different fields in the configuration window only once for each instance. The configuration window consists of basic, advanced, rendezvous/relays, and security tabs. The entries for basic and security tabs are the same for all three JXTA instances. Figure 4 displays the basic tab.
Figure 4. The basic tab in the JXTA configuration window

The basic tab contains a text box for the peer name. For example, Alice fills this box with the string "Alice" when she configures the JXTA instance that represents her on the JXTA network. When you configure the rendezvous and relay peers, you can give any names.
The basic tab also contains a checkbox named "Use proxy server." Check this box only if you are behind a firewall.
The security tab contains fields to enter logon information for the JXTA instance. Figure 5 shows the security tab containing secure username and password fields.
Figure 5. The security tab in the JXTA configuration window

The secure username is different from the peer name field in the basic tab. The peer name field specifies the name of the peer in the JXTA network, while the secure username field represents the login name of the particular JXTA instance. You need the secure username and password every time you start the JXTA instance.
For the sake of simplicity, you can use the same name (for example, Alice) as entries for the peer name and secure username fields.
Now let's see how to configure the advanced tab. The advanced tab has two sections, one for TCP and one for HTTP settings, as shown in Figure 6. The TCP and HTTP settings include the IP address and port number where this particular JXTA4JMS instance is listening. By default, JXTA configuration uses port 9700 for HTTP and 9701 for TCP.
Figure 6. The advanced tab in the JXTA configuration window

If you use different machines for the three JXTA instances, then you just need to specify the IP address of the respective machine. If you run more than one JXTA instance on the same machine, then you need to use different TCP and HTTP port numbers for each JXTA instance.
For example, when I tried this application on a single machine, I configured the JXTA4JMS instance for Alice with port number 9700 and 9701 for HTTP and TCP, respectively. I configured a second JXTA instance to act as both rendezvous and relay peers. This second instance listened at port numbers 9702 and 9703 for HTTP and TCP, respectively.
When you configure the rendezvous and the relay peers, you must do one more thing on the advanced tab: click the Enable Incoming Connections checkbox in the HTTP settings section. This allows other peers to have an HTTP connection with rendezvous and relay peers.
The rendezvous/relays tab shown in Figure 7 contains two sections, one for rendezvous peers and one for relay peers.
Figure 7. The rendezvous/relays tab in the JXTA configuration window

When you configure the relay peer, you must check the Act as a JxtaProxy and Act as a Relay checkboxes in the rendezvous/relays tab. When you configure the rendezvous peer you must check the Act as a Rendezvous checkbox.
You also need to give the IP address and port number of the rendezvous instance to the other two JXTA instances (the JXTA4JMS instance and the relay instance). For this purpose, you must fill the Available TCP rendezvous and Available HTTP rendezvous fields with the IP address and port number of the rendezvous peer.
Now a brief note on rendezvous peers. A real-world JXTA network contains many rendezvous peers. If Bob and Alice communicate over the JXTA network, each of them will know at least one rendezvous peer. It is not necessary that they both know the same rendezvous peer.
If Bob knows a rendezvous peer R1 and Alice knows rendezvous peer R2, and R1 and R2 rendezvous peers know a common rendezvous peer R3, then Bob and Alice are able to communicate seamlessly over the JXTA network. The JXTA network automatically manages the discovery of peers without Bob and Alice worrying about these details. This is an important feature of the JXTA technology.
Now let's discuss JXTA4JMS implementation details for the desktop side and then the mobile side.
Implement the desktop-side JXTA4JMS
I already discussed the function of various layers in the JXTA4JMS architecture, so I will now demonstrate the implementation of these layers. I move from bottom to top in this implementation discussion because upper layers use the functionality of the lower layers.
The JXTASearch class searches for a particular pipe on the JXTA network. The JXTASearch class searches the pipe in a synchronous way; this means that a call to the JXTASearch class does not return until it finds the pipe. The pipe you want to search is the pipe that the mobile-side has created, published, and is listening at. You use this pipe to send messages to the J2ME device.
The JXTASearch class contains a constructor and two methods: getPipeAdvertisement() and getOutputPipe(). In addition, the JXTASearch class also implements an interface named OutputPipeListener. According to the JXTA implementation, any class that wants to receive notifications about a pipe creation implements the OutputPipeListener interface. The OutputPipeListener interface has a single method named outputPipeEvent() that receives the pipe resolving notifications from the underlying JXTA implementation. You will soon see how the JXTASearch class implements the outputPipeEvent() method.
The JXTASearch constructor
The JXTASearch constructor (Listing 1) takes an instance of the PeerGroup class. The PeerGroup class is a convenient way to interact with a JXTA peer group. It contains methods to perform various tasks you might want to perform in a peer group. I'll describe how to use two methods of this class later. For now, just note that the JXTASearch constructor simply stores the PeerGroup object in a class-level variable (named peerGroup) for later use by the getPipeAdvertisement() and getOutputPipe() methods.
Listing 1. The JXTASearch constructor
public JXTASearch ( PeerGroup peerGroup ){
this.peerGroup = peerGroup;
}
|
The getPipeAdvertisement() method
The getPipeAdvertisement() method shown in Listing 2 takes a pipe name and searches for the advertisement corresponding to the pipe. The Listener class specifies the name of the pipe while it calls the getPipeAdvertisement() method.
The getPipeAdvertisement() method returns the pipe advertisement in the form of a PipeAdvertisement object.
Listing 2. The getPipeAdvertisement() method
public PipeAdvertisement getPipeAdvertisement ( String targetPipeName ) {
Enumeration enum = null;
PipeAdvertisement pipeAdvert = null;
DiscoveryService discoveryService = peerGroup.getDiscoveryService ();
try
{
while( true ){
discoveryService.getRemoteAdvertisements (
null,
DiscoveryService.ADV,
"Name",
targetPipeName,
5
);
enum = discoveryService.getLocalAdvertisements (
DiscoveryService.ADV,
"Name",
targetPipeName
);
if ( enum != null)
{
while (enum.hasMoreElements())
pipeAdvert = ( PipeAdvertisement ) enum.nextElement ();
}//if(enum != null)
if ( pipeAdvert != null )
break;
}//while( true )
}
catch ( Exception e ) {
e.printStackTrace();
}
return pipeAdvert;
}//getPipeAdvertisement()
|
The first order of business in the getPipeAdvertisement() method is to call the getDiscoveryService() method of the PeerGroup object stored in the constructor. This getDiscoveryService() method call returns a DiscoveryService object.
The DiscoveryService class represents a JXTA service known as discovery service. JXTA peers use the discovery service to search for different resources on the JXTA network. I use the JXTA discovery service to search for a particular pipe in the specified peer group.
If you want to use the JXTA discovery service, you need to author the XML search query and send the query to the rendezvous peer discussed earlier in Configure JXTA4JMS. Moreover, you must process incoming messages in response to your search query.
However, the JXTA implementation saves you from all this. The DiscoveryService class wraps the functionality of the discovery service nicely. You just call a few methods of the DiscoveryService class without worrying about what XML syntax JXTA requires and uses to respond. The JXTA implementation takes care of these low-level details.
After you have the DiscoveryService object, you can use its methods to perform the pipe search operation. The DiscoveryService class has a method named getRemoteAdvertisements() that sends a search query out on the JXTA network. The getRemoteAdvertisements() method takes five parameters:
-
The first parameter is an identifier. If you know the pipe identifier you are searching, you can provide the identifier as the first parameter to the
getRemoteAdvertisements()method to speed up the remote search operation. However, in this case, you don't know the identifier of the pipe you are searching. So you simply pass null as the value of the first parameter to thegetRemoteAdvertisements()method call. -
The second parameter specifies the type of resource you are searching (for example, an advertisement, a peer, or a peer group). You are searching for a pipe advertisement, so you simply use a static field named
ADVof theDiscoveryServiceclass. If you were searching for a peer or a peer group, you would specifyPEERorPEERGROUPstatic fields, respectively. -
The third parameter is the name of the attribute you want to search for. You are searching for a pipe advertisement with a particular name. So, you search for a pipe with a particular value of the
"Name"attribute. This is analogous to the second parameter to thesearch()method discussed in the "JXME programming" section of the first article. Therefore, you pass the string"Name"as the third parameter to thegetRemoteAdvertisements()method call. -
The fourth parameter is the name of the pipe you are searching (or you can say the value of the
"Name"attribute). You simply pass the name of the pipe as this value. -
The last parameter to the
getRemoteAdvertisements()method is the maximum number of search results you want to receive. You are not sure how many peers will respond to your search query and how many responses each peer will send. Therefore, you want to limit the search operation. You pass "5" as the value for the last parameter, which means you want to receive a maximum of five results from each peer.
The getRemoteAdvertisements() method does not return anything. It stores the search results in a local cache that the JXTA implementation maintains. Therefore, after calling the getRemoteAdvertisements() method, you must call another method named getLocalAdvertisements() to retrieve the search results from the local cache.
The getLocalAdvertisements() method takes three parameters: resource type, name of the attribute, and its value. These three parameters are the same as the second, third, and forth parameters to the getRemoteAdvertisements() method.
The getLocalAdvertisements() method returns an Enumeration object. The Enumeration object contains the search results in the form of a number of Advertisement objects.
You can expect different types of Advertisement objects in the Enumeration object the getLocalAdvertisement() method returns. As you search for pipe advertisements, you can expect that the Enumeration object, if not null, will include one or more PipeAdvertisement objects. The PipeAdvertisement object represents a pipe advertisement.
Let's go through the whole Enumeration and pick the last advertisement because the last is the most recent one.
The getOutputPipe() method
The getOutputPipe() method, shown in Listing 3, takes a PipeAdvertisement object as a parameter. Normally, this parameter is the same PipeAdvertisement object that you search for in the getPipeAdvertisement() method.
The getOutputPipe() method returns an OutputPipe object. This OutputPipe object represents the JXTA pipe corresponding to the pipe advertisement.
Listing 3. The getOutputPipe() method
public OutputPipe getOutputPipe (PipeAdvertisement pipeAdvert){
try
{
PipeService pipeSvc = peerGroup.getPipeService ();
pipeSvc.createOutputPipe ( pipeAdvert, this );
while ( true )
{
if ( outputPipe != null )
break;
}//while (true)
return outputPipe;
}//try
catch ( Exception e ){
e.printStackTrace();
}//catch
return null;
}//getOutputPipe()
|
Just as you need the JXTA discovery service to search for pipe advertisements, you need the JXTA pipe service to create an output pipe. The JXTA implementation provides a class named PipeService that wraps the functionality of the JXTA pipe service. You use the PeerGroup class's getPipeService() method to get the PipeService object.
The PipeService class has createInputPipe() and createOutputPipe() methods to create input and output pipes for message exchange. The PipeService class's createOutputPipe() method takes two parameters: PipeAdvertisement and OutputPipeListener objects. It creates a JXTA pipe using the advertisement and returns an OutputPipe object.
The JXTA pipe creation process occurs over the JXTA network. I won't get into those details, but the createOutputPipe() method can't wait for the pipe to be created, so it returns asynchronously.
The JXTA network confirms the pipe creation later in the form of a pipe-resolving event. According to the JXTA implementation, only the class that implements the OutputPipeListener interface can receive pipe-resolving event notification. The OutputPipeListener interface contains just one method named outputPipeEvent(). The JXTASearch class implements the outputPipeEvent() method so it can receive the notification.
When the notification arrives, the outputPipeEvent() method takes control. The outputPipeEvent() implementation is simple; Listing 4 shows that it only takes one line.The outputPipeEvent() method stores the newly created pipe in a class-level object named outputPipe.
Listing 4. The outputPipeEvent() method
public void outputPipeEvent ( OutputPipeEvent e ) {
outputPipe = e.getOutputPipe ();
}//outputPipeEvent()
|
To simplify the use of the JXTASearch class, I blocked the getOutputPipe() method in an infinite while loop that breaks only when the notification arrives. The infinite while loop keeps checking whether the outputPipeEvent() method has set the outputPipe class-level object. When the getOutputPipe() method finds the required OutputPipe object, it returns the same.
The JXTAToJMSMsgConverter class
The JXTAToJMSMsgConverter class takes a JXTA message and converts it into its equivalent JMS message. The JXTAToJMSMsgConverter class contains four methods (a constructor and three getter methods) as explained below:
The JXTAToJMSMsgConverter constructor
Listing 5 shows the JXTAToJMSMsgConverter constructor.
Listing 5. The JXTAToJMSMsgConverter constructor
public JXTAToJMSMsgConverter (
QueueConnectionFactory qConnFactory,
net.jxta.endpoint.Message jxtaMsg
) throws JMSException {
QueueConnection queueConnection = null;
try
{
//Creating a new JMS text message.
//Step 1:
queueConnection = qConnFactory.createQueueConnection ();
//Step 2:
queueSession =
QueueConnection.createQueueSession (
false,
Session.AUTO_ACKNOWLEDGE );
//Step 3:
jmsMessage = queueSession.createTextMessage ();
}
catch ( Exception e ) {
e.printStackTrace ();
}
MessageElement jmsRecipientElement, msgElement;
jmsRecipientElement = jxtaMsg.getMessageElement ("JMSRecipient");
msgElement = jxtaMsg.getMessageElement ("Message");
jmsRecipient = jmsRecipientElement.toString();
jmsMessage.setText (msgElement.toString());
}//JXTAToJMSMsgConverter
|
The constructor takes two parameters, a QueueConnectionFactory object named qConnFactory and a net.jxta.endpoint.Message object named jxtaMsg. I'll examine these objects more closely.
The Listener class instantiates a QueueConnectionFactory object and passes the object as the first parameter to the JXTAToJMSMsgConverter constructor. The QueueConnectionFactory is a connection factory and a JMS-administrated object. According to the JMS architecture, you need a QueueSession object to create a new JMS message. And you need a queue connection factory (a QueueConnectionFactory object) to create a QueueSession object. You will see how to create a JMS message from the connection factory later.
The net.jxta.endpoint.Message object (the second parameter to the JXTAToJMSMsgConverter constructor) is the JXTA message you want to convert to JMS format. Look at how the JXTAToJMSMsgConverter constructor works.
You must first create a new JMS message object, which is done in three steps. First, you call the QueueConnectionFactory object's createQueueConnection() method. This gives you a QueueConnection object. You then call the QueueConnection object's createQueueSession() method, which gives you a QueueSession object. In the third step, you call the QueueSession object's createTextMessage() method, which returns a javax.jms.TextMessage object. This javax.jms.TextMessage object is named jmsMessage. This is the JMS message object you now populate with data from the JXTA message. Notice these three steps in the "Creating a new JMS text message" in Listing 5.
At this point, note that a QueueSession object has methods to create various types of JMS messages. For example, you might want to create a message in binary form using the QueueSession class's createByteMessage() method. However, I chose to use the createTextMessage() method because I want to demonstrate the exchange of text messages in this article series.
Now take a look at how to extract data from the incoming JXTA message and populate the JMS message with that data.
The incoming JXTA message contains two parts: the name of the intended message recipient and the message itself. Each part of the JXTA message comes as an element in the JXTA message. The intended JMS recipient's name is wrapped inside an element called "JMSRecipient." The message is wrapped in an element named "Message.
You can call the net.jxta.endpoint.Message class's getMessageElement() method, passing the name of the element along with the method call. The getMessageElement() method returns a MessageElement object, which represents a single element of the JXTA message.
I explain the structure of an individual element in a JXTA message later. For now, just note from Listing 5 that the two MessageElement objects are named msgElement (for the object that wraps the message) and jmsRecipientElement (for the object that wraps the name of the recipient).
Now you can call the toString() method of each MessageElement object. This method returns the MessageElement's contents in string form.
You can then call the setText() method of the jmsMessage object (the javax.jms.TextMessage object created earlier) and pass the JXTA message contents along with the method call. This sets the JXTA message contents in the jmsMessage object. The jmsMessage object is now ready.
The getQueueSession() method
The getQueueSession() method (shown in Listing 6) returns the QueueSession object created in the constructor. You see that the Router class calls the getQueueSession() method to fetch the QueueSession object. The Router class uses this QueueSession object to send the text message to the JMS recipient.
Listing 6. The get QueueSession() method
public QueueSession getQueueSession() {
return queueSession;
}//getQueueSession()
|
The getMessage() method
The getMessage() method (shown in Listing 7) returns the javax.jms.TextMessage object named jmsMessage you created and populated in the constructor.
Lisitng 7. The getMessage() method
public javax.jms.TextMessage getMessage() {
return jmsMessage;
}//getMessage()
|
The getJMSRecepient() method
The getJMSRecipient() method seen in Listing 8 returns the name of the recipient you extracted from the incoming JXTA message in the constructor earlier.
Also note in Listing 8 that you have concatenated a string "jms/" as a prefix to the name of the JMS recipient. Later, you use the name of the recipient as a JMS queue name and simply send the message on the queue. The "jms/" string follows the Java convention that names of the JMS queues start with the "jms/" string.
Listing 8. The getJMSRecipient() method
public String getJMSRecipient() {
return "jms/" + jmsRecipient;
}//getJMSRecipient()
|
The JMSToJXTAMsgConverter class
JMSToJXTAMsgConverter is a message format converter class that takes a JMS message and produces a JXTA message. The JMSToJXTAMsgConverter class contains the methods described below.
The JMSToJXTAMsgConverter constructor
Now look at Listing 9, which shows the JMSToJXTAMsgConverter constructor.
Listing 9. The JMSToJXTAMsgConverter constructor
public JMSToJXTAMsgConverter ( String sender,
TextMessage jmsMsg
) throws JMSException {
StringMessageElement smeSender = null;
StringMessageElement smeMessage = null;
jxtaMessage = new net.jxta.endpoint.Message ();
smeSender = new StringMessageElement ("JMSSender", sender, null);
smeMessage = new StringMessageElement ("Message", jmsMsg.getText(), null);
jxtaMessage.addMessageElement (smeSender);
jxtaMessage.addMessageElement (smeMessage);
}//JMSToJXTAMsgConverter constructor
|
The JMSToJXTAMsgConverter constructor performs almost all the processing required by this class. It takes two parameters. The first parameter is the name of the JMS client who sent the JMS message. You include the name of the sender in the JXTA message you author in the JMSToJXTAMsgConverter constructor. This lets the J2ME recipient know which JMS client sent this message. The second parameter is a JMS message in the form of a TextMessage object.
JXTA messages are authored corresponding to the incoming JMS message. The JXTA message consists of message elements. Therefore, I'll first look at the internal details of an individual message element in a JXTA message.
The structure of a JXTA message element is similar to the structure of a JXME message element (explained in the first article's "Messaging between a relay and a JXME client" section).
A JXTA message element contains the following four fields:
- The name of the message element. In this case, you wrap the JMS message contents in an element named
"Message."Similarly, you wrap the name of the JMS sender in an element named"JMSSender."Note that the name of the element is optional, so you can omit it. However, giving every element a name makes processing straightforward and easier. - The optional MIME type of the message element. If this field is not specified, its value is assumed to be
"Application/Octet-Stream."In this case, the MIME type of the message you author is"text/plain." - The data you want to send. The message element, for example, contains the JMS message contents, and the sender element contains the name of the JMS sender.
- The optional signature. This field contains the message element's cryptographic signature. At the moment, JXME does not support the inclusion of signatures on the J2ME side. Therefore, there's no reason to include signatures when you author your JXTA message.
You have seen the structure of a JXTA message element. The JXTA implementation has a class named MessageElement that handles all types of JXTA message elements. In addition, you have a number of subclasses derived from the MessageElement class. Each subclass handles a specific type of message element. For example, the StringMessageElement class handles the message elements that carry textual strings (the MIME type of such messages is "text/plain").
You use the StringMessageElement class to author the JXTA messages' individual message elements. You wrap the name of the JMS message sender in a StringMessageElement object named smeSender. Similarly, you wrap the JMS message contents in another StringMessageElement object named smeMessage.
To author a message with the StringMessageElement class, you must first instantiate the StringMessageElement class by calling its constructor. The constructor takes three parameters. The first parameter specifies the name of the message element. The second parameter specifies the contents or data of the element. The third parameter specifies the cryptographic signature. You aren't using one, so you pass on null as the value of the third parameter.
Look at the JMSToJXTAMsgConverter constructor in Listing 9. The two StringMessageElement objects named smeSender and smeMessage are instantiated. While creating the smeMessage object, you need the contents of the incoming JMS message. You use the TextMessage object's getText() method to extract the JMS message contents and then pass the contents to the StringMessageConstructor to author the smeMessage object.
Now you need to wrap the two StringMessageElement objects in a JXTA message. For this purpose, you must first instantiate a net.jxta.endpoint.Message object named jxtaMessage that represents a complete JXTA message. Next, you call its addMessageElement() method twice, once for each of the two StringMessageElement objects.
The getMessage() method
You use the getMessage() method shown in Listing 10 to retrieve the JXTA message. The method simply returns the jxtaMessage object you prepared in the constructor.
Listing 10. The getMessage() method
public Message getMessage () {
return jxtaMessage;
}//getMessage()
|
The JMSSearch class performs the searching in the JMS network just like the JXTASearch does in the JXTA network. However, searching in the JMS network is quite simple because you can use the Java Naming and Directory Interface (JNDI) for searching the different JMS resources. You can use JNDI to search for resources such as messaging objects (queues and connection factories), databases, and mail sessions.
Here you use JNDI to search for a queue. Look at the JMSSearch class in Listing 11.
Listing 11. The JMSSearch class
public class JMSSearch
{
public JMSSearch (){
}
public QueueSender getQueueSender ( String queueName,
QueueSession queueSession ) {
InitialContext jndiContxt = null;
Queue outgoingQueue= null;
QueueSender qSender = null;
try
{
jndiContxt = new InitialContext ();
outgoingQueue= (Queue) jndiContxt.lookup ( queueName );
qSender = queueSession.createSender (outgoingQueue);
}//try
catch ( Exception e ) {
e.printStackTrace ();
}//catch
return qSender;
}//getQueueSender()
}//JMSSearch
|
The JMSSearch class contains just one method, getQueueSender(). This method takes two parameters, the name of the queue to search and a QueueSession object. This is the same QueueSession object introduced earlier in the discussion of the JXTAToJMSMsgConverter class's getQueueSession() method. I will explain the purpose of passing the QueueSession object as a parameter.
The getQueueSender() method returns the QueueSender object for this particular queue name.
The getQueueSender() method implementation, as you just saw in Listing 11, is simple. First, it instantiates an InitialContext object. This is a JNDI requirement. The InitialContext object has methods that perform the search operation for you.
Next, you simply call the lookup() method of the InitialContext class. In JNDI terminology, search is called lookup. The lookup() method takes the name of the queue as a parameter and returns a Queue object that represents the queue you are searching. The Queue object is named outgoingQueue because this Queue object represents the JMS queue of an intended message recipient. You use this queue later to send an outgoing message. For example, let's say Alice is on the road with her mobile-side JXTA4JMS and sends a message for Bob. Her desktop-side JXTA4JMS receives her message from the JXTA network, searches for Bob's queue on the JMS network, treats Bob's queue as the outgoingQueue object, and sends Alice's message to the outgoing (Bob's) queue.
Now you realize the importance of having the prefix "jms/" prepended before the queue name (recall that you prepended the "jms/" prefix in the JXTAToJMSMsgConverter class's getJMSRecepient() method). You don't just use JNDI lookup for searching JMS resources (other server-side modules use it for searching non-JMS resources like databases); so it's important to have some type of identifier to identify resources belonging to a particular group (for example, all resources belonging to the JMS network). If you don't have a string prepended to the queue name, you might confuse the queue name with some other non-JMS resource (such as a database table).
Although you have searched for the required Queue object, you want to do one extra step here. You want to create a QueueSender object from the Queue object you just searched. Later, the Router class simply uses the QueueSender object to send the message on the Queue.
To create a QueueSender object, you need a QueueSession object. This QueueSession object should be the same object you use while authoring the JMS message in the JXTAToJMSMsgConverter class. The Router class uses both the JXTAToJMSMsgConverter and JMSSearch classes, so I will explain in the discussion of the Router class below how it ensures that the appropriate QueueSession object is passed to the getQueueSender() method.
The last thing you do in the getQueueSender() method is call the QueueSession object's createSender() method. This method takes the Queue object you just created and returns a QueueSender object. The QueueSender is now complete. If you send your JMS message over this sender object, it automatically reaches the correct recipient. Now, take a look at how the Router class uses all the low-level support you have built to accomplish routing.
The Router class is the messaging junction of the JXTA4JMS messaging. The Router class uses the low-level layers (including format conversion and networking) and sends messages from the JMS network to the JXTA network and back. The purpose of the Router class is two-fold:
- It sends messages received from the JMS network to the mobile clients.
- It sends messages received from the JXTA network to JMS clients.
Therefore, it contains two methods: sendMessageToMobile() and sendMessageToJMS(). The Listener class calls the sendMessageToMobile() method whenever it receives a message from a JMS client. The sendMessageToMobile() method sends the message to the J2ME client over the JXTA network. Similarly, the Listener class uses the sendMessageToJMS() method whenever it receives a message from a mobile client.
Listing 12 shows the implementation of the Router constructor.
Listing 12. The Router constructor
public Router ( String peerName, PeerGroup peerGroup ) {
this.peerName = peerName;
this.peerGroup = peerGroup;
}//Router
|
The implementation of the Router constructor is simple. It just takes two parameters (name of the peer and its peer group) and stores them for later use by the two methods of the Router class.
The sendMessageToMobile() method
Listing 13 shows the implementation of the sendMessageToMobile() method.
Listing 13. The sendMessageToMobile() method
public void sendMessageToMobile(
String sender,
OutputPipe outputPipe,
TextMessage jmsMessage ) {
try
{
JMSToJXTAMsgConverter jxtaConverter =
new JMSToJXTAMsgConverter ( sender, jmsMessage );
net.jxta.endpoint.Message jxtaMessage = jxtaConverter.getMessage ();
outputPipe.send ( jxtaMessage );
}//try
catch ( Exception e ) {
e.printStackTrace ();
}//catch
}//sendMessageToMobile()
|
The sendMessageToMobile() method takes three parameters:
- The name of the JMS sender whose JMS message you send to the mobile client.
- An
OutputPipeobject that represents a JXTA pipe. (The mobile client listens on the other end of this pipe, so you send the message over this pipe.) I explain the mechanics of creating thisOutputPipeobject later while discussing theListenerclass. - The JMS message you want to send to the mobile client.
As you can easily guess, all you must do is translate the JMS message from a JMS format to a JXTA format and then send the message over the output pipe.
Therefore, as you just saw in Listing 13's sendMessageToMobile() method, you first instantiate a JMSToJXTAMsgConverter object and use it to get the JXTA representation of the incoming JMS message. Next, you simply call the OutputPipe object's send() method, passing the JMS message along with the method call. The OutputPipe object handles all the low-level processes of sending the message over the JXTA network.
The sendMessageToJMS() method
Now see listing 14, which shows the implementation of the sendMessageToJMS() method.
Listing 14. The sendMessageToJMS() method
public void sendMessageToJMS( QueueConnectionFactory qConnFactory,
net.jxta.endpoint.Message jxtaMessage )
{
try {
JXTAToJMSMsgConverter jmsConverter = new JXTAToJMSMsgConverter (
qConnFactory,
jxtaMessage
);
String jmsRecipient = jmsConverter.getJMSRecipient();
TextMessage jmsMessage = jmsConverter.getMessage();
QueueSession queueSession = jmsConverter.getQueueSession();
JMSSearch jmsSearch = new JMSSearch ();
QueueSender qSender = jmsSearch.getQueueSender ( jmsRecipient,
queueSession );
qSender.send ( jmsMessage);
}//try
catch ( Exception e ){
e.printStackTrace ();
}//catch
}//sendMessageToJMS
|
The sendMessageToJMS() method takes two parameters, a QueueConnectionFactory object and the JXTA message you want to send to the JMS recipient. Recall that the JXTAToJMSMessageConverter constructor requires the same two objects. Therefore, the sendMessageToJMS() method instantiates a JXTAToJMSMsgConverter object. It then calls the JXTAToJMSMsgConverter class's getJMSRecepient() and getMessage() methods to fetch the intended JMS message recipient's name and the incoming JXTA message's JMS form, respectively.
The sendMessageToJMS() method also calls the JXTAToJMSMsgConverter class's getQueueSession() method. This method returns a QueueSession object as already explained.
The sendMessageToJMS() method then instantiates a JMSSearch object and calls its getQueueSender() method. The getQueueSender() method takes the name of the intended JMS message recipient (a queue name) and the QueueSession objects as parameters. It returns a QueueSender object (as already explained in the discussion of the JMSSearch class).
Finally, you call the QueueSender object's send() method. The send() method takes the converted JMS message and sends the message to its intended JMS recipient.
The Listener class is designed to act as a continuously listening module. You want to design this class so that after it starts, it keeps listening to both JMS and JXTA networks. As soon as it receives a JMS message, it forwards the message to the J2ME device through the JXTA network. Moreover, as soon as it receives a message from the J2ME device over the JXTA network, it forwards the message to the concerned JMS client.
The Listener class implements a run() method that executes in a separate thread. The thread continuously listens for messages from both the JMS and JXTA networks. I will show how to implement the JMS and the JXTA message processing logic in the run() method. The Listener class contains some private helper methods. These methods assist in creating input and output JXTA pipes.
The createPipeAdvertisement() method
The createPipeAdvertisement() method shown in Listing 15 is a private helper method that creates a pipe advertisement and returns it in the form of a PipeAdvertisement object.
Listing 15. The createPipeAdvertisement() method
private PipeAdvertisement createPipeAdvertisement (String peerName) {
PipeAdvertisement pipeAd = null;
try
{
String fileName = peerName+".xml";
File file = new File ( fileName );
if ( file.exists() )
{
FileInputStream is = new FileInputStream (file);
if ( is.available() > 0 )
{
pipeAd =
(PipeAdvertisement) AdvertisementFactory.newAdvertisement(
new MimeMediaType( "text/xml" ),
is
);
}//if ( is.available() > 0)
}
else
{
pipeAd =
(PipeAdvertisement) AdvertisementFactory.newAdvertisement (
pipeAd.getAdvertisementType ()
);
pipeAd.setName (peerName);
pipeAd.setType ( "JxtaUnicast" );
pipeAd.setPipeID( (ID)net.jxta.id.IDFactory.newPipeID(
netPeerGroup.getPeerGroupID()
)
);
FileOutputStream os = new FileOutputStream ( fileName );
os.write ( pipeAd.toString().getBytes() );
os.close();
}//end of else
return pipeAd;
}//try
catch (Exception ex) {
ex.printStackTrace ();
}//catch
return null;
}//createPipeAdvertisement()
|
Creating a pipe advertisement is just an XML authoring task. You need to author the correct XML code according to the advertisement format defined by the JXTA protocols. Look at Listing 16, which shows the XML structure of a pipe advertisement.
Listing 16. A sample pipe advertisement in XML form
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jxta:PipeAdvertisement>
<jxta:PipeAdvertisement xmlns:jxta="http://jxta.org">
<Id>
urn:jxta:uuid-59616261646162614E50.....91E04
<Id>
<Type>
JxtaUnicast
<Type>
<Name>
AliceJMS
<Name>
</jxta:PipeAdvertisement>
|
JXTA provides classes for easy authoring of JXTA advertisements. The createPipeAdvertisement() method first checks whether a pipe advertisement already exists (from a previous pipe creation operation) in a file. If the file exists, you simply open the file and read its contents in a file input stream. Next, you use a JXTA class named AdvertisementFactory to create an advertisement from the input stream.
The AdvertisementFactory class has a static method named newAdvertisement() that takes two parameters. The first parameter specifies the MIME type of the advertisement you create. In this case, you want to create an XML advertisement, so the MIME type should be "text/xml." The second parameter specifies the input stream you created from the file.
The newAdvertisement() method returns an Advertisement object that you can cast into a PipeAdvertisement before returning back to the calling application.
Now, take a look at what you do if the pipe advertisement does not exist. Naturally, you must author a pipe advertisement from scratch. For this purpose, you use the overloaded form of the AdvertisementFactory class's newAdvertisement() method. This method takes just one parameter that specifies the type of advertisement you want to create. The newAdvertisement() method returns the pipe advertisement in the form of an Advertisement object. You cast the Advertisement object into a PipeAdvertisement object.
Now you set three parameters in the new pipe advertisement you just created. The three parameters are the name of the pipe you want to advertise, the type of the pipe (JXTAUnicast here), and the pipe identifier. You already know the first two parameters, but you need an identifier factory class named IDFactory to create a new identifier. The factory helps in creating unique identifiers.
After the new advertisement is ready, you save it in the disc file and return the PipeAdvertisement object.
The publishPipeAdvertisement() method
The publishPipeAdvertisement() method, shown in Listing 17, is a private helper method that takes a PipeAdvertisement object and publishes it over the JXTA network.
Listing 17. The publishPipeAdvertisement() method
private boolean publishPipeAdvertisement (PipeAdvertisement pipeAd)
{
DiscoveryService discSvc = netPeerGroup.getDiscoveryService ();
discSvc.remotePublish ( pipeAd,
DiscoveryService.ADV,
DiscoveryService.NO_EXPIRATION
);
return true;
}//publishPipeAdvertisement()
|
You always publish a pipe advertisement in a peer group. Moreover, all advertisements have an expiry time. Therefore, your advertisement appears in a specific peer group for a specified time period.
JXTA provides a service called a pipe service you can use to publish pipe advertisements. The JXTA implementation wraps the functions of pipe service in a class named PipeService. You can obtain an instance of the PipeService class by calling a PeerGroup object's getPipeService() method.
You simply call the PipeService class's remotePublish() method. The remotePublish() method publishes the advertisement on the JXTA network. It takes three parameters (the PipeAdvertisement object you want to publish, the type of advertisement, and the advertisement's expiry time).
The createInputPipe() method
The createInputPipe() method shown in Listing 18 takes a peer name and creates an input pipe to listen for incoming messages.
Listing 18. The createInputPipe() method
private void createInputPipe (String peerName) {
PipeAdvertisement pipeAd = createPipeAdvertisement (peerName);
if (pipeAd!= null) {
boolean published = publishPipeAdvertisement ( pipeAd );
if (published) {
PipeService pipeSvc = netPeerGroup.getPipeService ();
try {
inputPipe = pipeSvc.createInputPipe ( pipeAd );
}//try
catch (IOException io) {
io.printStackTrace ();
}//catch
}//if(published)
}//if (pipeAd!= null)
}//createInputPipe()
|
The createInputPipe() method creates an input pipe in three steps:
-
It calls the
createPipeAdvertisement()method and passes the peer identifier along with the method call. ThecreatePipeAdvertisement()method returns aPipeAdvertisementobject as already discussed. -
It then calls the
publishPipeAdvertisement()method, passing thePipeAdvertisementobject along with the method call. ThepublishPipeAdvertisement()method publishes the advertisement over the JXTA network. -
Finally, it creates an
InputPipeobject over the publishedPipeAdvertisementobject. For this purpose, you just call thePipeServiceclass'screateInputPipe()method, which returns aPipeServiceobject.
The createInputPipe() sets the newly created InputPipe object as a class-level object. You use this JXTA InputPipe object later to listen for incoming JXTA messages.
The searchAndCreateOutputPipe() method
You have seen how to create an input JXTA pipe. Now, look at how to create an output JXTA pipe.
The searchAndCreateOutputPipe() method you see in Listing 19 takes a peer name and creates an output pipe for the particular peer.
Listing 19. The searchAndCreateOutputPipe() method
private void searchAndCreateOutputPipe(String peerName) {
try {
JXTASearch jxtaSearch = new JXTASearch (netPeerGroup);
PipeAdvertisement pipeAdvert = jxtaSearch.getPipeAdvertisement (peerName);
if (pipeAdvert != null)
outputPipe = jxtaSearch.getOutputPipe(pipeAdvert);
}//try
catch ( Exception ex ) {
ex.printStackTrace ();
}//catch
}//searchAndCreateOutputPipe()
|
The output pipe creation is similar to the input pipe creation process. With input pipe creation, you first create a PipeAdvertisement object and then publish it. But when you create an output pipe object, you start by searching the already published PipeAdvertisement object. After you have the PipeAdvertisement object, the output pipe creation is similar to input pipe creation.
Therefore, output pipe creation consists of two steps: you search the published pipe advertisement and then create the output pipe using the pipe advertisement. You already have the functions for both these tasks in the JXTASearch class.
You call the JXTASearch class's getPipeAdvertisement() method, passing the peer name along with the method call. The getPipeAdvertisement() method returns the PipeAdvertisement object.
In the second step, you call the JXTASearch class's getOutputPipe() method. The getOutputPipe() method takes the PipeAdvertisement and returns an OutputPipe object.
You have seen how the helper methods in the Listener class create input and output pipes. Now, take a look at how the Listener class uses these helper methods.
The Listener constructor
Listing 20 shows the implementation of the Listener constructor:
Listing 20. The Listener constructor
public Listener ( String userName,
String connectionFactoryName ) {
try {
//Preparing for JMS.
javax.naming.InitialContext jndiContxt = new InitialContext();
queueConnFactory =
(QueueConnectionFactory) jndiContxt.lookup (connectionFactoryName);
Queue incomingQueue = (Queue) jndiContxt.lookup ("jms/"+userName);
QueueConnection queueConnection =
queueConnFactory.createQueueConnection ();
QueueSession queueSession =
queueConnection.createQueueSession ( false,
Session.AUTO_ACKNOWLEDGE
);
queueReciver = queueSession.createReceiver ( incomingQueue );
queueConnection.start();
//Preparing for JXTA.
netPeerGroup = PeerGroupFactory.newNetPeerGroup ();
router = new Router( userName, netPeerGroup );
createInputPipe (userName + "JMS");
searchAndCreateOutputPipe(userName + "J2ME");
}//try
catch ( Exception ex ) {
ex.printStackTrace ();
}//catch
}//Listener
|
The Listener constructor prepares for listening on the JMS and JXTA networks. It takes two parameters: the name of the user and the name of a JMS connection factory. The username specifies the name of the user currently using JXTA4JMS (for example, Alice). This name is the same on both JMS and JXTA networks.
The name of the connection factory indicates which JMS connection factory the Listener class uses to perform search operations on the JMS network. Some higher-level application (such as the simple main() method shown in Listing 21) specifies the two parameters while instantiating the Listener object.
Listing 21. The main() method
public static void main(String argv[])
{
if ( argv.length < 2 )
{
usage();
return;
}//if ( argv.length < 2)
Listener listener = new Listener ( new String (argv[0]),
new String (argv[1])
);
Thread listenerThread = new Thread (listener);
listenerThread.start();
}//main()
|
The Listener constructor first makes its preparations to start listening on the JMS network. For this purpose, it performs the following operations:
-
The first step is to search for the connection factory using JNDI. Searching for connection factories is the same as searching for a JMS queue (explained in the discussion of the
JSMSearchclass'sgetQueueSender()method). You just instantiate anInitialContextobject and call itslookup()method. -
Now, search for the JMS queue, on which the
Listenerclass will listen. Call itincomingQueue. Note that theoutgoingQueueobject created earlier in thegetQueueSender()method is the one on which you send JXTA messages. TheincomingQueueobject you search here is the one on which you will listen for incoming JMS messages. This means, for example, if Alice is on the road with her mobile-side JXTA4JMS, the desktop-side JXTA4JMS keeps on listening for incoming JMS messages on herincomingQueueobject. Whenever it receives an incoming message (say, from Bob) onincomingQueue, it sends the message to Alice (or her mobile-side JXTA4JMS). -
The next step is to use the connection factory (a
QueueConnectionFactoryobject) to instantiate a new JMS connection. AQueueConnectionobject represents this connection. You can simply call theQueueConnectionFactoryobject'screateQueueConnection()method, which does not take any parameters and returns aQueueConnectionobject. -
Now you use the
QueueConnectionobject to create a JMS queue session. You can create several queue sessions on the same connection. To create a new JMS session, you call theQueueConnectionobject'screateQueueSession()method. -
You then use this
QueueSessionobject to create aQueueReceiverobject. You call theQueueSessionobject'screateReceiver()method, passing theincomingQueueobject along with the method call. ThecreateReceiver()method returns aQueueReceiverobject. You use theQueueRecveiverobject to receive messages from theincomingQueue.Recall from the discussion about the
JMSSearchclass'sgetQueueSender()method that you need aQueueSenderobject to send messages to a queue.QueueSessionobjects create bothQueueSenderandQueueReceiverobjects.Note that you use two different
QueueSessionobjects to create theQueueSenderandQueueReceiverobjects because you use different queues for sending and receiving messages. At the moment, you create a receiver because you know you must check for incoming messages from only one queue (the queue of the user currently using the JXTA4JMS implementation, which is Alice). On the other hand, theQueueSenderyou created in theJMSSearchclass'screateQueueSender()method was for the particular JMS client (Bob) for whom you received the JXTA message.
The Listener constructor is now ready to listen for, or receive, incoming JMS messages from the user's queue. Now let's see how the Listener constructor prepares to receive messages from the JXTA network.
You want to create an input pipe to receive JXTA messages from the mobile client. Similarly, you also create an output pipe to send messages to the mobile client. Note that you already know the names of input and output pipes because the two pipes represent the same user currently using the JXTA4JMS implementation. The input and output pipe names differ in postfix. You allocate postfix "JMS" to the input pipe name because the input pipe represents a desktop-side JXTA4JMS pipe, and the mobile-side JXTA4JMS uses this name to search the desktop-side JXTA4JMS pipe. You allocate postfix "J2ME" to the output pipe because the output pipe represents the mobile-side JXTA4JMS pipe, and the desktop-side JXTA4JMS uses this name to search the mobile-side JXTA4JMS pipe.
The Listener constructor follows these steps to create input and output pipes:
-
The first step is to instantiate a
PeerGroupobject. You are working in the default net peer group, so you create aNetPeerGroupobject instead of aPeerGroupobject.NetPeerGroupis a subclass of thePeerGroupclass, so you can use it instead of thePeerGroupclass. -
Now you can instantiate a
Routerobject by calling its constructor. As previously explained, theRouterclass's constructor takes the username and aPeerGroupobject as parameters. -
In step three, you create a JXTA pipe you then publish (or advertise) over the JXTA network. It is an input pipe the J2ME mobile client uses to send messages to the desktop client.
-
The
createInputPipe()method (already discussed) performs all the steps required to create an input pipe. You just call thecreateInputPipe()method and pass the username to the method. -
The last step is to search for and create an output pipe. The desktop client uses this pipe to send messages to the mobile client. To create an output pipe, you can use the
searchAndCreateOutputPipe()method, which I discussed earlier.
I have explained all the preparations the Listener constructor performs. Now, see how the Listener class uses these preparations for JXTA4JMS messaging.
The run() method
I have written a run() method in the Listener class that runs as a separate thread, as you can see in Listing 22 .
Listing 22. The run() method
public void run()
{
while( true ) {
int counter = 0;
while ( counter <= 10 ) {
try {
javax.jms.Message message = queueReciver.receive (1000);
if ( message != null ) {
if ( message instanceof TextMessage )
{
TextMessage txtMsg = (TextMessage) message;
router.sendMessageToMobile (
txtMsg.getStringProperty("Sender"),
outputPipe,
txtMsg
);
}
}//if ( message != null )
counter = counter + 1;
}//try
catch ( Exception ex ) {
ex.printStackTrace ();
}//catch
}//while (counter <= 10)
counter = 0;
while ( counter <= 5 )
{
net.jxta.endpoint.Message msg = null;
try {
msg = inputPipe.poll ( 1000 );
if ( msg != null )
router.sendMessageToJMS( queueConnFactory, msg );
counter = counter + 1;
}//try
catch ( InterruptedException iEx ) {
iEx.printStackTrace ();
}//catch
}//while (counter <= 5)
counter = 0;
}//while(true )
}//run()
|
The run() method continuously listens on the JMS and JXTA sides for incoming messages. As soon as it receives an incoming message from a JMS client, it forwards the message to the mobile J2ME client. Similarly, when it receives a message from the mobile client (on the JXTA network) it sends the message to the intended JMS recipient.
The run() method starts with an infinite while loop, inside which I have written two inner while loops. The first loop listens for messages coming from JMS clients, and the second while loop listens for messages from the mobile client. The logic in the loops is simple. It uses the Router class for appropriate routing of messages.
Implement the mobile-side of JXTA4JMS
I described the responsibilities of different J2ME classes in mobile-side JXTA4JMS while discussing JXTA4JMS architecture in The JXTA-for-JMS architecture. Now, I'll examine the implementation details of these classes.
When you implement the J2ME side of JXTA4JMS, you use JXME classes (PeerNetwork, Message and Element). I already described these classes in this series' first article.
The JXTA4JMSMessage class authors and processes JXTA4JMS messages. A JXTA4JMS message is a JXME message that can be customized. An application uses the JXTA4JMSMessage class whenever it wants to send or receive a message to or from a desktop peer.
Recall from the "Messaging between a relay and a JXME client" section of the first article that all JXME messages consist of elements. All JXME messages contain certain common elements (for example, the EndpointSourceAddress element discussed in the first article's Listing 3). In addition, a JXME message might contain additional application-specific elements. A JXTA4JMS message contains two of the following three JXTA4JMS-specific elements:
-
The
Messageelement wraps the contents of the message. This element is always present in all JXTA4JMS messages. -
The
JMSSenderelement wraps the name of the JMS user who sends this message. Naturally, this element is present only in incoming messages received from the desktop-side JXTA4JMS. -
The
JMSRecipientelement wraps the name of the JMS user who is the intended JXTA4JMS message recipient. As you can guess, this element is only present in outgoing messages sent to the desktop-side JXTA4JMS.
The JXTA4JMSMessage object might represent either incoming or outgoing messages. The JXTA4JMSMessage class has two constructors, a one-argument constructor and a two-argument constructor. The one-argument constructor helps to process incoming messages, while the two-argument constructor authors outgoing messages. Let me show you how they work.
The one-argument JXTA4JMSMessage constructor
Listing 23 shows the one-argument JXTA4JMSMessage constructor. It takes a JXME Message object as a parameter. This Message object represents an incoming message from the desktop JXTA4JMS client. The Message object contains the Message and JMSSender elements.
Listing 23. The one-argument JXTA4JMSMessage constructor
public JXTA4JMSMessage (Message msg) {
processMessage(msg);
}//JXTA4JMSMessage
|
The constructor simply passes the Message object to a private helper method named processMessage(). The processMessage() method extracts the contents of the Message and JMSSender elements and stores the contents in two class-level variables named messageText and sender, respectively.
Listing 24 shows the processMessage() method; it's similar to the processMessage() method discussed in Listing 19 of the first article, so I won't explain the details here.
Listing 24. The processMessage() method
private void processMessage (Message msg) {
try {
for (int j=0; j < msg.getElementCount(); j++) {
Element e = msg.getElement(j);
if ((e.getName()).equals("JMSSender"))
sender = new String(e.getData());
else if ((e.getName()).equals("Message"))
messageText = new String (e.getData());
}//for (int j=0;)
if (sender != null) {
Element[] msgElements = new Element [2];
msgElements[0] = new Element (
"JMSSender",
(sender).getBytes(),
null,
null
);
msgElements[1] = new Element (
"Message",
messageText.getBytes(),
null,
null
);
message = new Message ( msgElements );
}//if (sender!=null)
}//try
catch(NumberFormatException ne){
ne.printStackTrace();
}//catch
}//processMessage()
|
After storing the contents of the two elements, the processMessage() method also authors a Message object corresponding to the contents of the JMSSender and Message elements. I explained the authoring of a JXME message in Listing 26 of the first article.
The two-argument JXTA4JMSMessage constructor
Listing 25 shows the implementation of the two-argument JXTA4JMSMessage constructor. Two strings form the two arguments of the constructor. The first string (recipient) is the name of the JMS recipient, and the second string (messageText) is the actual message you want to send.
Listing 25. The two-argument JXTA4JMSMessage
public JXTA4JMSMessage (String recipient, String messageText) {
Element[] msgElements = new Element [2];
this.recipient = recipient;
this.messageText = messageText;
msgElements[0] = new Element (
"JMSRecipient",
(recipient).getBytes(),
null,
null
);
msgElements[1] = new Element (
"Message",
messageText.getBytes(),
null,
null
);
message = new Message ( msgElements );
}//JXTA4JMSMessage
|
The constructor stores the recipient and the messageText strings in two class-level variables named recipient and messageText, respectively. It then authors a Message object corresponding to the recipient and message fields.
The getter methods of the JXTA4JMSMessage class
The JXTA4JMSMessage class also contains four getter methods, as Listing 26 shows:
-
The
getMessage()method returns theMessageobject that either of the two constructors authored. A higher level class (for example, theJXMEMIDletclass) uses this method to get theMessageobject. -
The
getMessageText()method returns the textual contents of the message. -
The
getRecipient()method returns the name of the outgoing message's JMS recipient (in case theJXTA4JMSMessageobject wraps an outgoing message). -
The
getSender()method returns the name of the incoming message's JMS sender (in case theJXTA4JMSMessageobject wraps an incoming message).
Listing 26. The four getter methods of the JXTA4JMSMessage class
public Message getMessage(){
return message;
}//getMessage()
public String getSender(){
return sender;
}//getSender()
public String getRecipient(){
return recipient;
}//getRecipient()
public String getMessageText() {
return messageText;
}//getMessageText()
|
The DataStore class provides simple J2ME record store services. The JXTA4JMSMessagingClient class uses the DataStore class's services.
The DataStore constructor (Listing 27) takes the name of a J2ME record store and opens it for read and write operations.
Listing 27. The DataStore constructor
public DataStore(String storeName) {
try {
recStore = RecordStore.openRecordStore ( storeName, true );
} catch ( Exception e ) {
e.printStackTrace();
}
}//DataStore
|
The DataStore class also has a deleteStore() method (shown in Listing 28) that deletes a J2ME record store.
Listing 28. The deleteStore() method
public void deleteStore(String storeName) {
try{
recStore.closeRecordStore();
RecordStore.deleteRecordStore(storeName);
} catch ( Exception e ) {
e.printStackTrace();
}
}//deleteStore()
|
Additionally, the DataStore class has a setRecord() method, shown in Listing 29, that takes two parameters and stores them in a J2ME record store. The two parameters are a pipe identifier and the time when the pipe was first published over the JXTA network. JXME does not help you identify whether a particular pipe has expired, so you must keep track yourself.
Listing 29. The setRecord() method
public void setRecord (long time, String pipeId) {
String record = (Long.toString(time)) +"@"+ pipeId;
try {
recStore.addRecord ( record.getBytes(), 0, record.getBytes().length );
recStore.closeRecordStore();
} catch ( Exception e ){
e.printStackTrace();
}//catch
}//setRecord()
|
The getPipeId() and getAdvPublishTime() methods you see in Listing 30 retrieve the pipe identifier and its publishing time, respectively, from the J2ME record store.
Listing 30. The getPipeId() and getAdvPublishTime() methods
public String getPipeId() {
try {
if ( recStore.getNumRecords() > 0 ) {
String record = new String( recStore.getRecord (1) );
int index = record.indexOf("@");
String pipeId = record.substring (index+1,record.length());
return pipeId;
}//if ( recStore.getNumRecords() > 0)
}//try
catch (Exception e) {
e.printStackTrace();
}//catch
return null;
}//getPipeId ()
public long getAdvPublishTime() {
long timeInMillis = 0L;
try {
if ( recStore.getNumRecords() > 0 ) {
String record = new String( recStore.getRecord (1) );
int index = record.indexOf("@");
timeInMillis = Long.parseLong( record.substring (0,index) );
}
}//try
catch (Exception e) {
e.printStackTrace();
}//catch
return timeInMillis;
}//getAdvPublishTime()
|
I want to avoid detailed discussion of J2ME record-store programming, so I've provided a link to an excellent developerWorks article in the Resources section. The article demonstrates how to work with J2ME record stores.
The JXTA4JMSMessagingClient class
The JXTA4JMSMessagingClient class is the core of J2ME-side JXTA4JMS. It provides all JXME messaging features to a JXTA4JMS client application. The features include:
- JXTA relay connection
- Input and output pipe creation
- JXTA pipe (where the JXTA4JMS client listens) searching
- Message sending to the desktop JXTA4JMS client
- JXTA relay polling for incoming messages
The JXTA4JMSMessagingClient class uses the PeerNetwork class to perform messaging with the relay. The JXTA4JMSMessagingClient also uses the JXTA4JMSMessage class to author and process JXTA4JMS messages.
The JXTA4JMSMessagingClient class also implements the Runnable interface that contains just one method named run(). The run() method always executes in a separate thread. This thread polls the relay to check for incoming messages.
A higher-level application (for example, a MIDlet) specifies the time period after which this thread polls the relay. After polling, the JXTA4JMSMessagingClient class displays the incoming message to the user and then sleeps until the next polling. You will soon see how the JXTA4JMSMessagingClient class implements the polling logic in the run() method.
The JXTA4JMSMessagingClient class consists of a constructor and a method named sendMessage(). In addition, it also contains a few private helper methods to perform low-level JXME messaging. I already covered all JXME-related topics in the first article's "JXME programming" section, so I won't talk about that here.
The JXTA4JMSMessagingClient constructor
Listing 31 shows the JXTA4JMSMessagingClient constructor, which takes four parameters:
-
The
relayURLparameter specifies the URL of the relay through which you want to communicate with the JXTA network. -
The
peerNameparameter specifies the name of the peer using mobile-side JXTA4JMS (for example, Alice). -
The
timePeriodparameter is an integer that represents a time period in milliseconds. (This is the period between two polls. TheJXTA4JMSMessagingClientclass polls and then waits for this amount of time before the next poll. TheJXTA4JMSMessagingClientclass continues doing this.) -
The
TextFieldparameter is aTextFieldobject that represents a text field on the J2ME device screen. (TheJXTA4JMSMessagingClientupdates this text field with the incoming message's contents every time it receives a message.)
Listing 31. The JXTA4JMSMessagingClient constructor
public JXTA4JMSMessagingClient(
String relayURL,
String peerName,
int timePeriod,
TextField textField )
{
this.timePeriod = timePeriod;
this.textField= textField;
String pipeName = new String(peerName + "J2ME");
try {
byte[] persistentState = null;
peerNetwork = PeerNetwork.createInstance ("J2MEClient");
persistentState = peerNetwork.connect (relayURL, persistentState);
DataStore dataStore = new DataStore(pipeName);
if (dataStore.getPipeId() == null) {
String pipeId = createPipe(pipeName);
listenToPipe(pipeId);
dataStore.setRecord( System.currentTimeMillis(), pipeId);
}//if(dataStore.getPipeId() == null )
else
{
long difference =
(System.currentTimeMillis() - dataStore.getAdvPublishTime());
if (difference > expiryTime) {
dataStore.deleteStore(pipeName);
dataStore = new DataStore(pipeName);
String pipeId = createPipe(pipeName);
listenToPipe(pipeId);
dataStore.setRecord ( System.currentTimeMillis(), pipeId);
}//if (difference > expiryTime)
else
listenToPipe(dataStore.getPipeId());
}//else
outputPipeId = searchPipe (peerName + "JMS");
}//try
catch ( Exception e ) {
e.printStackTrace();
}//catch
}//JXTA4JMSMessagingClient
|
The JXTA4JMSMessagingClient must first store the timePeriod and textField parameters as class-level variables. This allows you to use these parameters later when the JXTA4JMSMessagingClient polls the relay for incoming messages.
Then the JXTA4JMSMessagingClient constructor connects to the relay. Your next task is to create an input pipe and ask the relay to start listening to it. The name of the pipe is the username with the postfix "J2ME." (Recall you allocated pipe names earlier during the Listener constructor discussion; the postfix "JMS" is allocated for the pipe on which the desktop client listens, and the postfix "J2ME" is allocated for the pipe on which the J2ME client listens.)
Now check the record store to see whether a valid pipe identifier for the user already exists. If it exists, you can create a pipe with the same pipe identifier. If the pipe identifier does not exist or has expired, request the relay to issue another pipe identifier. After you have the pipe identifier, request the relay to start listening for messages arriving on the pipe.
When you are finished with the input pipe, search for the pipe over which the desktop client creates and listens for messages coming from the J2ME client (the name of the pipe is the username with a postfix "JMS"). For the J2ME client, this is an output pipe.
Recall that I discussed how to request an identifier, search for pipe, and request the relay to start listening on a pipe in the first article's "JXME programming" section. Three helper methods (searchPipe(), createPipe(), and listenToPipe(), shown in Listing 32) in the JXTA4JMSMessagingClient class, perform these tasks.
Listing 32. The searchPipe(), createPipe(), and listenToPipe() helper methods
private String searchPipe(String pipeName) {
String pipeId = null;
try {
int messageID = peerNetwork.search (
PeerNetwork.PIPE,
"Name",
pipeName,
-1);
Message msg = null;
do {
msg = peerNetwork.poll(5000);
if (msg != null) {
pipeId = processMessage(msg, messageID, "result");
return pipeId;
}//if (msg != null)
} while (msg!=null);
}//try
catch (IOException ie) {
ie.printStackTrace();
}//catch
return null;
}//searchPipe()
private String createPipe(String pipeName) {
String pipeId = null;
try {
int messageID = peerNetwork.create (
PeerNetwork.PIPE,
pipeName, null,
PeerNetwork.UNICAST_PIPE);
Message msg = peerNetwork.poll(5000);
if (msg == null)
return null;
pipeId = processMessage(msg, messageID, "success");
}//try
catch (IOException ie) {
ie.printStackTrace ();
}//catch
return pipeId;
}//createPipe()
private String listenToPipe (String pipeId) {
String listenOk = null;
try {
int messageID = peerNetwork.listen (pipeId);
Message msg = peerNetwork.poll(5000);
if (msg == null)
return null;
listenOk = processMessage (msg, messageID, "success");
}//try
catch (IOException ie) {
ie.printStackTrace ();
}//catch
return listenOk;
}//listenToPipe()
|
The sendMessage() method
The sendMessage() method in Listing 33 below takes a JXTA4JMSMessage object as a parameter and sends the message to the desktop peer over the output pipe created in the JXAT4JMSMessagingClient constructor.
The ultimate recipient of this JXTA4JMSMessage is a JMS user, whose information is stored in the JXTA4JMSMessage object. As described in Messaging from a JMS client to a J2ME client, the message first reaches the relay, where it's translated to JXTA format; the relay then forwards the message to the desktop JXTA4JMS client, which translates the message to JMS format; the desktop JXTA4JMS client finally sends the message to the JMS queue of the ultimate recipient.
Listing 33. The sendMessage() method
public void sendMessage (JXTA4JMSMessage message) {
try {
int messageID = peerNetwork.send (outputPipeId, message.getMessage());
Message msg = peerNetwork.poll(5000);
if (msg != null)
processMessage(msg, messageID, "success");
}//try
catch (IOException ie){
ie.printStackTrace();
}//catch
}//sendMessage()
|
The run() method
The JXTA4JMSMessagingClient implements the Runnable interface that consists of just one method named run(). This method periodically polls the relay to check for any incoming messages.
The run() method implementation (shown in Listing 34) is simple. It polls the relay and then sleeps for the time period specified by the timePeriod parameter to the JXTA4JMSMessagingClient constructor. When the sleep time finishes, it polls the relay again.
When it receives a message as a result of polling, it uses the JXTA4JMSMessage class to process the message and extract the contents of the message. Finally, it displays the incoming message to the user on the textField object that was passed as the last parameter to the JXTA4JMSMessagingClient constructor.
The run() method executes in a separate thread. This means that the polling mechanism runs independently of sending messages.
Listing 34. The run() method
public void run() {
Message msg = null;
String message = null;
JXTA4JMSMessage jxmeMsg = null;
stringBuffer = new StringBuffer();
while( true ) {
try {
Thread.sleep(timePeriod);
msg = peerNetwork.poll (2000);
if (msg != null) {
jxmeMsg = new JXTA4JMSMessage (msg);
if ((jxmeMsg.getMessage()!=null) &&
(jxmeMsg.getSender()!=null))
{
textField.setLabel("Message From: "+jxmeMsg.getSender());
textField.setString(jxmeMsg.getMessageText());
}
}//if(msg != null)
}//try
catch (Exception e) {
e.printStackTrace();
}//catch
}//while(true)
}//run()
|
I included a sample MIDlet called JXMEMIDlet in this article's source code download. The sample MIDlet demonstrates the simple use of mobile-side JXTA4JMS.
Listing 35 shows the JXMEMIDlet constructor that instantiates a JXTA4JMSMessagingClient object passing hard-coded values of the peer name, the address of the relay, the time interval between polls, and a text area object to the JXTA4JMSMessagingClient constructor.
Listing 35. The JXMEMIDlet constructor
public JXMEMIDlet() {
try {
tfMessageFromSender = new TextField ( "", "", 40, TextField.UNEDITABLE);
jxmeMessagingClient = new JXTA4JMSMessagingClient(
relayURL,
"Alice",
15000,
tfMessageFromSender);
showMessagingForm();
}//try
catch ( Exception e ){
e.printStackTrace();
}//catch
}//JXMEMIDlet
|
The startApp() method you see in Listing 36 of JXMEMIDlet starts the polling thread by instantiating a Thread object and then calling the Thread.start() method.
Listing 36. The startApp() method
public void startApp(){
Thread thread = new Thread (jxmeMessagingClient );
thread.start();
}//startApp()
|
Also look at the commandAction() method of JXMEMIDlet in Listing 37, which shows how to use JXTA4JMS to send messages to the JMS network.
Listing 37. The commandAction() method
public void commandAction (Command c, Item item) {
if ( c == CMD_SEND ) {
try {
if (tfRecipientName.getString().equals( "" )) {
Alert alert = new Alert(
"Error",
"Please enter recipient name for sending message",
null,
AlertType.ERROR
);
alert.setTimeout( 1000 );
display.setCurrent ((Screen) alert);
}//if (tfRecipientName.getString().equals( "" ))
else {
JXTA4JMSMessage message = new JXTA4JMSMessage(
(tfRecipientName.getString()),
tfMessageForRecipient.getString());
jxmeMessagingClient.sendMessage (message);
}
}//try
catch ( Exception e ) {
e.printStackTrace();
}
}//if (c == CMD_SEND)
}//commandAction()
|
Figure 8 shows a screenshot of the message receiving and authoring the user interface. When JXTA4JMS receives an incoming message through the polling thread, the name of the sender (for example, Bob) appears in the "Message From:" field. The contents of the incoming message appear in the next line.
When Alice wants to send a message to a JMS user (for example, Bob), she types the name of the JMS user in the "Message To:" field, types the message in the "Message" field, and then presses Send. JXTA4JMS then carries the message to the JMS recipient.
Figure 8. The message receiving and authoring the user interface

To make it easy to try out JXTA4JMS, I wrote a simple JMS test client application and included it in this article's source code download. The JMS client application can represent a JMS user. The readme file in the source code download explains how to test the communication between Alice and Bob using JXTA4JMS.
This article discussed the architecture of JXTA4JMS. It also explained the JXTA4JMS implementation and demonstrated how JXTA4JMS can provide wireless connectivity to JMS users.
| Name | Size | Download method |
|---|---|---|
| wi-jxta2source.zip | HTTP |
Information about download methods
-
Read the first article of this series, "Wireless messaging with JXTA, Part 1: Using JXTA technology" (developerWorks, November 2004).
-
For more information on JMS, see the JMS page at Sun's Web site. Also, read this comprehensive tutorial on JMS.
-
Find more information on JNDI at Sun's Web site.
-
Visit the JXTA home page to find out more about this technology.
-
Read "Making P2P interoperable: The JXTA story" (developerWorks, August 2001) to learn more about JXTA protocols. Also see "Making P2P interoperable: The JXTA shell command" (September 2001) and "Making P2P interoperation: Creating JXTA systems" (developerWorks, April 2002).
-
Download the JXTA shell application from the JXTA site.
-
Find more information on JXME from the JXTA-J2ME (JXME) Platform Project.
-
Download JXME libraries from the JXTA site.
-
I used the J2EE SDK to test this article's code. You can also visit the official J2EE page.
-
I used J2ME record store in this article. For more information on record-store programming refer to this "J2ME record management store" article (developerWorks, May 2002).
-
I implemented multiple thread J2ME programming. Read "J2ME and Multithreading" (J2ME.org, September 2004), which discusses multiple threading in J2ME applications.
Faheem Khan is an independent software consultant specializing in Enterprise Application Integration (EAI) and B2B solutions. He can be reached at fkhan872@yahoo.com.