dcsimg

JXTA: Peer Into the Future

These days, it's increasingly rare to begin a new Java project without looking at one or more pre-existing frameworks, collections of class libraries that provide the underlying structure for an application and enforce good programming practices. There are frameworks for enterprise computing (EJB), Web applications (Struts), business process modeling (Naked Objects), graphical user interfaces (JFace), and many other areas of development. A well-designed framework provides a sturdy structure upon which to build applications.

These days, it’s increasingly rare to begin a new Java project without looking at one or more pre-existing frameworks, collections of class libraries that provide the underlying structure for an application and enforce good programming practices. There are frameworks for enterprise computing (EJB), Web applications (Struts), business process modeling (Naked Objects), graphical user interfaces (JFace), and many other areas of development. A well-designed framework provides a sturdy structure upon which to build applications.

However, frameworks are a trade-off: you get reliability and speed of development, yet often give up flexibility and the joy of inventing something hands-on. But, ultimately, there’s an overwhelming benefit: a framework captures the gobs of brainpower that its developers applied to the problem.

The JXTA framework, an open source project sparked by Bill Joy and Mike Clary of Sun Microsystems, tackles one of the hardest challenges in current programming: the development of peer-to-peer software (P2P). JXTA is a set of XML protocols that provide the building blocks necessary to support peer-to-peer networking services, such as the formation of decentralized networks, presence detection, message exchange, and security.

Because of the project’s name and its origins at Sun, many developers have the perception that JXTA is a Java project. That’s not entirely the case: Java is used for the reference implementation of the protocols, but JXTA — named after the word “juxtapose” — is intended to be language independent. There are fledgling projects afoot to support JXTA with C, Perl, Python, Ruby, and SmallTalk.

The Java implementation of the project is available from the JXTA web site, which can be found at http://www.jxta.org. The code is provided under the Sun Project JXTA Software License, which is comparable to the Apache Software License. The current version, 2.1, requires Java 1.3.1 and the Jakarta logging library, Log4J.

JXTA ’round the Corner

JXTA creates a virtual network that abstracts the physical networks required to support peer-to-peer communication. As senior architect Bernard Traversat of Sun describes it:

“JXTA can be viewed as a TCP/IP stack on steroids, addressing current IP and DNS network deployment limitations for building P2P applications. … As Java implies platform independence, and XML implies language independence, then JXTA implies network independence.”

A JXTA implementation consists of several resources created and delivered as XML:

* Peers are networked devices that can self-organize and are uniquely identified by peer IDs.

* Peer groups are collections of those peers.

* Services are functional tasks that one peer can perform for other peers.

* Advertisements announce how to make connections and interact with other peers.

* Pipes are asynchronous channels for communication between peers.

All of this operates at a high level of abstraction, freeing a developer from the need to deal with the networks, protocols, and services that make it possible.

Furthermore, a peer group does not have to be connected to every other peer group; as long as two groups can reach intermediaries who can relay messages, the pair can be peers. And all peers belong to the Net Peer Group, which encompasses all users of JXTA. Peers also can create, discover, or join as many other groups as needed, each of which may have its own membership policies. Peer groups can offer six kinds of services:

* Access verifies that a peer has the credentials to request a service from another peer.

* Discovery finds peers, peer groups, pipes, and services.

* Membership is a service that accepts or rejects a peer seeking to join a group.

* Monitoring observes other group members for such purposes as metering the usage of a service.

* Pipe creates and maintains pipes between group members.

* Resolver delivers queries from one peer to any peer that might be able to handle it.

Services can be offered by individual peers or by groups operating in concert with each other.

Before a peer can make use of a service, it may need to download and install it from another peer, a process that JXTA developers compare to finding and installing a web browser plug-in. This “learned behavior” is called a module, and is language- and platform-independent. A module could be a Java class or JAR, a dynamic DLL, XML documents, or any other format supported by a JXTA implementation. The process of equipping a peer with a new service is automatic, provided that the peer is part of a group that offers it and has authorization to receive it.

There’s a lot more to say about the JXTA architecture, which is extremely ambitious in scope. To give you some idea of its breadth, the Java implementation is composed of 88 packages and more than 520 classes.

Listings One through Three contain the source code for two JXTA applications and an XML file required by both. The HelloPeer class in Listing One creates a peer that can send a “Hello” message to another peer in its group. The HelloListener class in Listing Two creates a peer that can receive that message. The XML file pipeadv.xml in Listing Three serves as the announcement for that service. It’s created by HelloPeer and received by HelloListener.




Listing One: The HelloListener class

import java.io.*;
import net.jxta.document.*;
import net.jxta.endpoint.*;
import net.jxta.exception.*;
import net.jxta.peergroup.*;
import net.jxta.pipe.*;
import net.jxta.protocol.*;

public class HelloListener implements
PipeMsgListener {
static PeerGroup netPeerGroup = null;
private final static String ADFILE =
“/usr/java/pipead.xml”;
private final static String TAG = “HelloMessage”;
private PipeAdvertisement pipeAd;
private PipeService piper;
private InputPipe inpipe = null;

public static void main(String[] arguments) {
HelloListener hl = new HelloListener();
hl.run();
}

public HelloListener() {
try {
netPeerGroup =
PeerGroupFactory.newNetPeerGroup();
} catch (PeerGroupException e) {
System.out.println(“Could not create net peer
group”);
e.printStackTrace();
System.exit(1);
}
piper = netPeerGroup.getPipeService();
System.out.println(“Reading ad file ” +
ADFILE);
try {
FileInputStream fis = new
FileInputStream(ADFILE);
MimeMediaType mxml = new
MimeMediaType(“text/xml”);
pipeAd = (PipeAdvertisement)
AdvertisementFactory.newAdvertisement(mxml,
fis);
fis.close();
} catch (Exception e) {
System.out.println(“Could not read pipe ad”);
e.printStackTrace();
System.exit(1);
}
}

public void run() {
try {
System.out.println(“Creating an input pipe”);
inpipe = piper.createInputPipe(pipeAd, this);
} catch (Exception e) {
System.out.println(“Could not create input
pipe.”);
return;
}
if (inpipe == null) {
System.out.println(“Could not open input
pipe”);
System.exit(1);
}
System.out.println(“Waiting for messages”);
}

public void pipeMsgEvent(PipeMsgEvent event) {
Message message = null;
try {
message = event.getMessage();
if (message == null)
return;
} catch (Exception e) {
System.out.println(“Could not receive
message”);
e.printStackTrace();
return;
}
MessageElement element =
message.getMessageElement(null, TAG);
System.out.println(“Received message ” +
element.toString());
}
}




Listing Two: The HelloPeer class

import java.io.*;
import net.jxta.discovery.*;
import net.jxta.document.*;
import net.jxta.endpoint.*;
import net.jxta.exception.*;
import net.jxta.peergroup.*;
import net.jxta.pipe.*;
import net.jxta.protocol.*;
import net.jxta.rendezvous.*;

public class HelloPeer implements Runnable,
OutputPipeListener,
RendezvousListener {

private final static String ADFILE =
“/usr/java/pipead.xml”;
private final static String TAG = “HelloMessage”;
static PeerGroup netPeerGroup = null;
private PipeAdvertisement pipeAd;
private DiscoveryService discoverer;
private PipeService piper;
private RendezVousService rdver;
public static void main(String[] arguments) {
HelloPeer hp = new HelloPeer();
hp.run();
}
public HelloPeer() {
try {
netPeerGroup =
PeerGroupFactory.newNetPeerGroup();
} catch (PeerGroupException pge) {
System.out.println
(“Could not create net peer group”);
pge.printStackTrace();
System.exit(1);
}
piper = netPeerGroup.getPipeService();
rdver = netPeerGroup.getRendezVousService();
rdver.addListener(this);
System.out.println(“Reading ad file ” +
ADFILE);
try {
FileInputStream fis =
new FileInputStream(ADFILE);
MimeMediaType mxml = new
MimeMediaType(“text/xml”);
pipeAd = (PipeAdvertisement)
AdvertisementFactory.newAdvertisement
(mxml, fis);
fis.close();
} catch (Exception e) {
System.out.println(“Could not read pipe ad”);
e.printStackTrace();
System.exit(1);
}
}

public synchronized void run() {
try {
System.out.println
(“Creating an output pipe”);
piper.createOutputPipe(pipeAd, this);
if (!rdver.isConnectedToRendezVous()) {
System.out.println
(“Waiting for a rendezvous”);
try {
wait();
System.out.println
(“Connected to a rendezvous”);
piper.createOutputPipe(pipeAd, this);
} catch (InterruptedException e) {
// received notification
}
}
} catch (IOException ioe) {
System.out.println
(“Could not create output pipe”);
ioe.printStackTrace();
System.exit(1);
}
}

public void outputPipeEvent(OutputPipeEvent event) {
OutputPipe outpipe = event.getOutputPipe();
Message message = null;
try {
String hello = “Hello from peer ” +
netPeerGroup.getPeerName();
message = new Message();
StringMessageElement sme = new
StringMessageElement(TAG, hello, null);
message.addMessageElement(null, sme);
System.out.println
(“Sending message ” + hello);
outpipe.send(message);
} catch (Exception e) {
System.out.println(“Could not send message”);
e.printStackTrace();
System.exit(1);
}
outpipe.close();
}

public synchronized void
rendezvousEvent(RendezvousEvent event) {
if (event.getType() == event.RDVCONNECT) {
notify();
}
}
}




Listing Three: The pipead.xml file

<!DOCTYPE jxta:PipeAdvertisement>

<jxta:PipeAdvertisement xmlns:jxta=”http://jxta.org“>
<Id>

urn:jxta:uuid-
DF281EDB2CE94B9B8B4284F84B1A252F43459ED6FA974AC49B79BB67
77AF0EA204
</Id>
<Type>
JxtaUnicast
</Type>
<Name>
HelloExample
</Name>
</jxta:PipeAdvertisement>

The two Java classes share a lot of behavior in common. In their constructor methods, they call the PeerGroupFactory class method newNetPeerGroup() to create a NetPeerGroup object that represents the collection of all JXTA peers:


netPeerGroup =
PeerGroupFactory.newNetPeerGroup();

A PeerGroupException is thrown if the net group is inaccessible.

The services of the net peer group can be retrieved with several of the object’s instance methods:


PipeService piper =
netPeerGroup.getPipeService();
RendezVousService rdver =
netPeerGroup.getRendezvousService();

The HelloPeer class needs both of these services. The rendezvous service brings peers together even if they aren’t directly accessible to each other. It can pass along requests to let one peer know about another peer’s services. The pipe service delivers the message.

The HelloListener class only needs the pipe service.

Next, both classes read in the pipeadv.xml file in Listing Three, creating a PipeAdvertisement object to represent it:


FileInputStream fis = new
FileInputStream(“pipead.xml”);
MimeMediaType mxml = new
MimeMediaType(“text/xml”);
PipeAdvertisement pipeAd =
(PipeAdvertisement)
AdvertisementFactory.newAdvertisement
(mxml, fis);
fis.close();

As mentioned above, advertisements are XML documents that publicize the existence and availability of a peer service. A peer seeks out the resources of the other members of its group (or groups), receiving the advertisements and caching them locally for a duration that can be specified in the ad. When one peer knows about an advertisement and another doesn’t, it can share this information.

Two peers know they are dealing with the same service because of its ID, one of the elements in the ad. JXTA provides its own unique addressing system rather than relying upon DNS or another naming service. IDs begin with the identifier urn:jxta:uuid- followed by a long hexadecimal sequence. A new unique ID can be generated by calling the newUUID() class method of UUIDFactory. This ID can be specified when creating a new pipe advertisement with the createPipeAdvertisement() method of the AdvertisementUtilities class.

Each class has a run() method that creates the pipe for the exchange of messages with peers. The pipes are created by calling a PipeService object’s createOutputPipe() or createInputPipe() method, respectively:


piper.createOutputPipe(pipeAd, this);

piper.createInputPipe(pipeAd, this);

After an output pipe has been created, HelloPeer makes sure it’s connected to a rendezvous service by calling the pipe service’s isConnectedToRendezVous() method:


if (!rdver.isConnectedToRendezVous()) {
// code to wait
}

When a peer joins a group for the first time, it looks for an existing member that serves as a rendezvous. If none can be found, the peer takes on this responsibility unless it’s been configured otherwise. A peer that can’t or won’t take on this role is an edge peer — since JXTA is designed for any device, including memory- and processor-limited processors, there’s no requirement for all peers to support the same level of functionality.

The HelloPeer class waits for a rendezvous using thread notification: wait() pauses execution of the thread until its notify() or notifyAll() method is called. The class uses a RendezvousListener to receive an event that signifies a new connection to a rendezvous:


public synchronized void
rendezVousEvent(RendezVousEvent event) {
if (event.getType() == event.
RDVCONNECT){notify();
}
}

This example illustrates one pedantic quirk of JXTA that may be nettlesome to a few programmers: the text RendezVous doesn’t come as naturally to English-typing fingers as Pipe, Peer, Service, or the other elements of the architecture.

Both HelloPeer and HelloListener signify their readiness to exchange messages through events. The OutputPipeEvent indicates that a pipe is ready to deliver a message. The PipeMsgEvent indicates that a message has been received.

The pipe event used by HelloListener holds the message that triggered the event, which is another XML document. The event’s getMessage() method returns a Message object, which has a getMessageElement() method that retrieves the contents of an XML element in the message:


Message message = event.getMessage();
MessageElement element =
message.getElement(null, “HelloMessage”);
System.out.println(“Received message ” +
element.toString());

The output pipe event used by HelloPeer creates and delivers a message: the Message() constructor is called, then a textual message element is created to hold the text of a “Hello” message. This element is added to the message, which is then delivered:


Message message = new Message();
StringMessageElement sme = new
StringMessageElement(“HelloMessage”);
message.addMessageElement(null, sme);
outpipe.send(message);

Before the HelloPeer and HelloListener classes are compiled, the reference to pipeadv.xml should be expanded to indicate the folder location along with the filename.

The HelloListener class should be run first. When a JXTA Java application is run for the first time, a JXTA Configurator window opens so that your computer can be configured to connect to the peer-to-peer network.

One thing that must be done is to set up a username and password. Though the default configuration often works, it can be finicky when firewalls are involved. A how-to guide is available on the JXTA web site at http://platform.jxta.org/java/confighelp.html.

The Configurator creates a subfolder named .jxta with a configuration file, cached advertisements, and other files. The configuration file is formatted in XML and can be edited directly.

If the HelloPeer class is being tested on the same computer where HelloListener runs, it should be moved to a different folder. This causes the JXTA Configurator to open again; use it to set up different ports for JXTA — such as 9702 and 9703 instead of 9700 and 9701.

There’s a huge amount of documentation on JXTA at the project’s Web site, including an indispensable 140-page Java programmer’s tutorial called Project JXTA 2.0: Java Programmer’s Guide.

Although JXTA is challenging to undertake, the guide contains several Java applications under 200 lines in length that demonstrate how to create peers and peer groups, exchange information securely, communicate in two directions with a peer, and offer services.

Shock and Awe

The JXTA framework is billed as a solution to one of the most challenging and intricate areas of software development. Peer-to-peer networking offers as many obstacles to programmers as it does to politicians, courts, and the entertainment industry.

The formulation of networks on the fly, with or without a central server, through firewalls and with authentication and encryption, atop the Internet and other networks — just writing a spec for a project like that is enough to inspire shock and awe. But JXTA offers all that and more.

It’s tough to evaluate whether JXTA meets its lofty goal without putting it through the full rigors of development. A hot debate among Java webloggers is whether JXTA is a better choice than Jabber, the open, XML protocol for instant messaging and presence notification on the Internet, offered from http://www.jabber.org. One of the selling points of Jabber is that it is smaller in scope than JXTA, which seeks to make it possible for any digital device to peer with any other. At any time. On any network. In any way.

JXTA is still heavily rooted in Java because it lacks much in the way of implementations in other languages (which may not be a drawback to readers of this column). Whether or not JXTA proves to be the right choice for a newly launched P2P effort, the framework offers an education in the subject that may be completely without peer.




Rogers Cadenhead is a Web application developer and the coauthor of
Teach Yourself Java 2 in 21 Days, Third Edition from Sams Publishing. To contact Cadenhead, visit his weblog at http://www.cadenhead.org/workbench. You can download the Java source code used in this month’s column from http://www.linux-mag.com/downloads/2003-11/java.

Comments are closed.