Achieving Synergy, ObjectSpace's Voyager ORB 3.0

  SideBar
Short Line   MugMugMugMugMug
Version Reviewed: 3.0
Current Version: 3.1.1

Cup rating system:
5 Outstanding
4 Very Good
3 Good
2 Acceptable
1 Poor



REVIEW IN A NUTHELL
Anyone developing distributed clients will want to check this out. It implements every imaginable distributed computing feature with only a few abstractions and a small and easy-to-use API. No other product on the market makes client programming easier.

Price:
Voyager Application Server = $1,999.00, Voyager ORB Professional = $799.00

Vendor Info
ObjectSpace
ObjectSpace Inc.
14850 Quorum Drive Suite 500
Dallas, TX 75240
v: 800.OBJECT.1 or 972.726.4100
f: 972.726.4200
[email protected]
www.objectspace.com


THE FIELD OF software development evolves so rapidly and changes so often that it's nearly impossible to keep up. It's easy to feel bombarded by acronyms (CORBA, DCOM, JSP, ASP, EJB, UML) and overwhelmed by (often competing) theories, standards, and technologies.

From time to time, a coalescing idea comes along that manages to tie it all together, to leverage what exists and raise the level of abstraction a couple of notches—to find just the right synergy. The invention of structured programming languages is an example of a synergistic breakthrough; so are relational databases, graphical user interfaces (GUIs), Internetworking, and, more recently, object-oriented (OO) programming and the Java platform.

I examined a product that, if not exactly the cause of a paradigm shift, is part of this kind of synergistic breakthrough. With the Voyager ORB 3.0 from ObjectSpace, distributed computing has never been easier, cheaper, or as widely available.

Voyager is a full-blown Java ORB architecture designed to support the development of powerful distributed computing systems. The product uses the Java Virtual Machine (JVM) to load classes at runtime to create mobile objects and autonomous agents. A complete and seamless distributed computing framework, it supports remote invocation, remote pass by value, distributed events, naming services, object mobility, autonomous agents, runtime object extensions, object activation, distributed class and resource serving and loading, distributed security, distributed garbage collection, distributed timers, advanced messaging, multicasting, and replaceable networking protocols.

The Voyager framework provides all of the above with only a few abstractions, a very small API, and no need for interface definition languages (IDLs) or management of stubs or proxies. Voyager works from interfaces defined in pure Java and automatically generates and distributes whatever stubs or proxies it needs at runtime. All this functionality is provided by a single 500K JAR file of Java that users can download free (for internal use) from the ObjectSpace Web site.

The Voyager ORB comes in two versions: the Voyager ORB Core, which is free for "internal" use (it can't be distributed to third parties); and Voyager ORB Professional, the commercial version. Voyager ORB Pro includes everything in the Voyager ORB, plus a configuration framework, JNDI integration, CORBA naming service, and support for ultra-light (15K) clients. It also includes the Voyager Management Console, a Java program that enables central configuration of services offered by different clients. The just-released Voyager Pro version 3.1 (see sidebar) adds dynamic XML, connection management, persistent replicated directory, directory load balancing, and support for DCOM.

ObjectSpace sells several products that work with Voyager to provide enhanced functionality, including Voyager Transactions, Voyager Security, and the Voyager Application Server. These products provide CORBA-compatible transaction support, advanced security, and an EJB-capable application server that can be licensed separately.

OO for Everyone
Voyager truly is something of a breakthrough in distributed programming. It combines the various features of Java to produce something greater than the parts. What Voyager does has never been done before (in a commercially available product, anyway) and was almost impossible to do until the advent of Java.

The beauty of the Voyager ORB is the way it manages to hide the many complexities of distributed programming by leveraging the strengths of the Java platform. Voyager uses introspection to discover the features of whatever class users want to make distributable (at runtime). It then generates any required wrapper classes on-the-fly by writing the bytecodes to memory and registering the just-created classes with the JVM. Voyager lets users deploy code to a single location by leveraging Java's class-loading mechanism; it supports flexible and custom security by embracing standard Java security; it supports pass-by-value and object mobility by leveraging Java serialization. All this happens under the hood, without any involvement required from the programmer. The program just makes a remote call and classes get dispatched to the right place, connections are made, and objects are serialized and reconstructed at the destination.

Dynamic Aggregation
At the core of Voyager's magic is its ability to attach secondary objects, or facets, to a primary object at runtime. This is what the Voyager documentation calls Dynamic Aggregation. Classes don't have to be modified in any way for instances to act as facets or to be extended by them.
IEmployee employee = new 
  Employee( "joe", "234-44-2678" );
IFacets facets = Facets.of( employee );
IAccount account = (
  IAccount) facets.of(IAccount );
account.deposit( 2000 );
Classes used as facets do not need to be related in any way to their "primaries"; but Voyager allows different implementations of a given facet to be attached to different primaries by using an algorithm that searches for likely facet implementations using the primary's class name.

Those familiar with COM (the Microsoft Common Object Model) programming will probably recognize a similarity between COM interface querying and facets. The difference is that Voyager's Dynamic Aggregation allows for new facets to be attached to an object at runtime, even when the source code for the primary is not available. Voyager's features such as Proxies, Mobility, and Facets are implemented using Dynamic Aggregation.

Remote Invocation
In OO programming, remote invocation refers to the ability to invoke methods on remote objects (objects that exist in a different machine) as if they were local. Remote invocation is usually implemented through proxies, which provide a façade that hides the mess of the underlying plumbing. Voyager excels in this regard in that this plumbing is automatically generated at runtime, for any class, as required. This means that classes do not have to be modified in any way to be remote enabled. Most notably, they don't have to descend from a framework class nor do they have to implement a specific interface.

Method calls made to a proxy are forwarded to its related object. If the object is remote, the arguments are serialized using the standard Java serialization mechanism and reconstructed at the destination. By default, parameters are passed by value, except for actual parameters that are already proxies, or those with classes that implement
com.objectspace.voyager.IRemote or java.rmi.Remote;
these are passed by reference. The same rules are applied for method return values. Exceptions that occur during a remote method call are caught at the remote site and rethrown locally.

Voyager also implements advanced messaging. A one-way message call returns immediately, discarding the return value if any. For "future" messages, the call returns a proxy to the result, which can be polled or read in a blocking fashion. Timeouts can be specified for future messages, and users can request that a proxy is returned in place of the actual object:
try {
Object args = new Object[] {"XYZ"};
Result result =
Future.invoke(
  market, "getStock", args, true);
result.setTimeout(5000);
IStock stock = (
  IStock) result.readObject()
catch (TimeoutException e) {
  //...
Voyager implements distributed security on top of standard Java security. This means that security managers, permissions, signing, encryption, and custom security policies are all available to distributed applications. It also means that it is not necessary to study the intricacies of yet another security system to deploy secure distributed applications with Voyager. For additional security, Voyager allows the classes that carry out communications between hosts to be replaced with secure versions like SSL. Voyager Pro also provides a security implementation that's more aware of distribution, and supports a central configuration and remote management.

Naming Services
The usual way of getting initial references to remote objects in distributed applications is to use a naming service or registry. Voyager implements a distributed, hierarchical, and federated naming service that is tightly integrated with the rest of the framework. References to named remote objects are obtained passing the target host, port, and name to the Namespace.lookup() method:
IStockMarket market = 
	  (IStockMarket) Namespace.lookup(
	    "//host:8000/NASDAQ");
Names can be bound to objects at creation time. The following call would bind the name NASDAQ to the newly created object and export in the default (local) server:
IStockMarket market = 
	  (IStockMarket) Factory.create(
	    "StockMarket", "NASDAQ");
Bindings can also be done when an object is exported via the Proxy.export() method, or later, by using Name space.bind():
Proxy.of(market).export("NASDAQ");
Namespace.bind("NYSE", market2);
Hierarchical directories can be created by using slash-separated ("/") paths for the names:
Namespace.bind(
	  "services/exchanges/
	    NYSE", market2);
A distributed directory can be created by binding one directory to a name in a remote host:
Directory exchanges = 
	  new Directory();
exchanges.put("NASDAQ", market1);
exchanges.put("NYSE", market2);
Namespace.bind(
	  "//remotehost:8000/
    exchanges", exchanges);
The same API is used to access CORBA and JNDI directories just by using different URLs. For example:
IStockMarket market = 
	  (IStockMarket) Namespace.lookup(
	    "rmi://host/NASDAQ");
A Voyager server also acts as a CORBA and JNDI naming server. Clients need only use the usual bootstrap code for naming services.

Multicast Mechanism
Voyager implements an efficient multicast mechanism that supports the publish/subscribe metaphor. To participate in multicast, a program places the objects that will be receiving the messages in a special container called a Subspace. A Subspace can be connected to other local or remote subspaces to form a Space. Messages sent to a Subspace are copied to neighboring Subspaces before being delivered to objects in the local Subspace. The usual way to send a multicast message to objects in a Space is to obtain a multicast proxy of the desired type. Method calls over the proxy translate into multicast messages and eventually into calls of the same method over any object that implements the same interface:
ISubspace subspace = (
	  ISubspace) Namespace.lookup( 
	    "//localhost:8000/Subspace1" );
IBroker allBrokers = (
  IBroker) subspace1.
    getMulticastProxy( "IBroker" );
allBrokers.stockChanged("XYZ");
Voyager's publish/subscribe allows programs to register event handlers with a space, much like it's done in Java's Abstract Windowing Toolkit (AWT). Programs subscribe to specific topics by adding instances of the Subscriber class to a space. Topics are strings or names separated by periods, like "stock.XYZ.up." A Subscriber acts on messages that match the topic pattern passed to its constructor and forwards the received event to its associated PublishedEventListener.

Object Mobility
Voyager allows remotely-enabled objects to be moved from host to host. Mobility allows moving objects that exchange large numbers of messages closer to each other, reducing network traffic. Mobility can also be used for server balancing and for clustering. Moving an object in Voyager is very simple:
IStockMarket market = 
	  (IStockMarket) 
	    Namespace.lookup("NASDAQ");
Mobility.of(market).moveTo(
	  "//another_server:7000");
Objects with classes that implement Voyager's IMobile interface will receive pre- and post-notifications of departures and arrivals when they are moved.

Voyager provides a special type of mobility in the form of agents. Agents can roam around the network by themselves carrying the necessary information to load their related classes and other resources. When an agent is set to be autonomous, it won't be garbage collected even after there are no more references to it.
IBroker broker = 
	  Factory.create("Broker");
IAgent agent = Agent.of(broker);
agent.setAutonomous(true);
agent.setResourceLoader(
  new URLResourceLoader(
	    "http://thishost:8000"));
	      agent.moveTo("//markethost", 
	        "roamAndBuy");
Existing proxies to objects that are moved are updated the next time they are used.

Resource Serving
Making sure that the Java class files for a distributed application are in sync on all the deployment hosts can be a nightmare. Voyager solves this problem by allowing any HTTP (Web) server as a source for class files and other resources:
VoyagerClassLoader.addURLResource(
	  "http://serverforclasses/path");
VoyagerClassLoader.
	  addURLResource(
	    "http://yetanotherserver");
Voyager itself can act as an HTTP server, so there's no need to deploy any other software for serving class files. Voyager also allows for classes to be loaded from signed JAR files or from custom class loaders that implement the IResourceLoader interface.

With resource serving, all the classes that implement a distributed application's functionality can be deployed to a single host or a pool of them. Resource serving also allows for mobile agents to roam around a network, arriving at hosts on which class files required for running the agent would normally not be available.

Activation
Object activation means the ability to store objects to permanent storage and resurrect them afterward on request, even when the underlying program has been restarted. Voyager's activation architecture is independent of the persistence mechanism used. To enable activation for a given class of objects, a program provides an activator and registers it with Voyager's activation manager.
ILibrary library = new Library();
LibraryActivator activator = 
	  new LibraryActivator(library);
Activation.register( activator );
Namespace.bind( "Library", library );
An activator is an object whose class implements the IActivator interface. It is responsible for providing a string that acts as a key and uniquely identifies activated objects and for reconstructing the original objects when requested. Voyager provides support for taking snapshots of activated objects and of their facets. After an object has been enabled for activation, the process of activation is transparent to clients that hold references to activated objects.

Given the simplicity of Voyager programming, its CORBA features are especially appealing. I tested the product's compatibility with the Visigenic ORB (VisiBroker) bundled with Inprise's JBuilder Enterprise 3. In the first test, a VisiBroker client tried to interact with the server program from Voyager's simple "Bank" example. The second test used Voyager as a client against VisiBroker's own Bank example.

To perform the first test, I copied the files from Voyager's Bank example to a different directory. Then IDL files were produced from the Java interfaces and exception classes using Voyager's CGEN utility. The IDL files were then used to generate new Java interfaces and CORBA stubs and skeletons using VisiBroker's IDL2JAVA tool. Then a client was written using the VisiBroker libraries. Both programs were compiled, the server was started, and the client was run. This experiment didn't work. The VisiBroker client halted with an exception no matter what I tried.

The second test applied the same process to VisiBroker's Bank example. This time the experiment ran on the first try.

Conclusion
The only real complaint I have about Voyager ORB is the lack of documentation available about ORB semantics. I've heard complaints from other users that the ORB has scalability problems due to the way it assigns threads to requests, but my experience with the product revealed no such shortcoming. In any event, that kind of problem is easy enough to foresee and simple enough to avoid with the right kind of documentation.

I strongly recommend this product to anyone developing distributed clients. No other product I'm familiar with makes client programming easier. Developers working on distributed applications, especially those involving applets and/or servlets, also would love this product. And anyone who wants to experiment and learn about distributed computing could hardly go wrong downloading this product and taking it out for a spin. Voyager ORB Core is free, after all. It's easy to install, simple to learn, and even simpler to use.


Juancarlo Añez is director of IT at Modelistica in Caracas, Venezuela. He can be contacted at [email protected].


Building a Simple Distributed App
The following example is designed to give you a taste of the Voyager APIs and to demonstrate how simple it can be to program distributed applications. Running Voyager is as simple as setting the CLASSPATH and executing the distributed application. Voyager also includes a stand-alone server that can be executed from the command line. These are the steps I went through to build a simple distributed application that exercises object mobility:
  1. Define a Java interface that describes the objects we'll be moving around:
    public interface ITalker {
    		   void say(String message);
    		}
    		
  2. Provide a straightforward implementation of the interface:
    public class Talker
    		implements ITalker, java.io.Serializable {
    		    public void say(String message) {
    		        System.out.println(message);
    		    }
    		}
    		
    The class must implement java.io. Serializable to be able to move around the 'Net.
  3. Compile the two Java files and start the Voyager ORB:
    voyager 8000 -r
    The number (800), tells Voyager which port receives requests. The "-r" parameter tells the ORB to act as an HTTP server for classes in its CLASSPATH.
  4. Copy the ITalker.class to a different machine and write, compile, and run the following Java program:
    import com.objectspace.voyager.*;
    		import com.objectspace.voyager. 
    		  loader.*;
    		import com.objectspace.voyager. 
    		  mobility.*;
    		
    		public class Houdini {
    		    // change "momo" to the name 
    		of the actual remote machine
    		    static final String peer = 
    		"//momo:8000";
    		
    		    public static void main(String[] 
    		args) throws Exception {
    		        // allow required classes to be 
    		loaded from peer
    		
    		VoyagerClassLoader.addURLResource(
    		  "http:" +peer);
    		      // start the local Voyager ORB
    		      Voyager.startup();
    		      try {
    		           // create an instance of our 
    		ITalker implementation
    		         ITalker talker = (ITalker) 
    		           Factory.create("Talker");
    		         // do the magic
    		         talker.say("1,2...");
    		
    		Mobility.of(talker).moveTo(peer);
    		           talker.say("3! Applause 
    		             please!");
    		
    		System.out.println("Applause 
    		  please!");
    		      } finally {
    		        Voyager.shutdown();
    		      }
    		   }
    		}
    		
This small program started a client ORB, created an object from a class file provided by a different machine, moved the object to another machine, and remotely invoked a method on the (now) remote object. I used a text editor, a Java compiler, the Java Runtime Environment, and Voyager. There was no IDL, and all the required proxies, stubs, and other plumbing were provided by Voyager at runtime by leveraging the networking, serialization, and introspection capabilities of Java.