A 20 minute crash-course in Voyager 3.3 By G.D. Georgovassilis Introduction During our search for JAVA agent platforms we made an encounter with Objectspace’s Voyager (www.objectspace.com/voyager.html) , which has been discussed extensively in [1]. In this document I’ll investigate Voyager’s technical nature by discussing a simple demonstration. What do you need? We tested the Voyager ORB platform on Windows 95, 98 and 2000 with JDK 1.2.x and 1.3. The manufacturer claims it works with JDK 1.1.8 and later versions under Windows, SunOS and Linux. Voyager is shipped in two bundles (ORB, ORB Professional) none of which is free-of-cost for commercial use. However they all contain documentation as well as programming examples. Voyager’s installation is simple; yet the CLASSPATH environment variable has to be completed, as usual. Information on necessary settings is provided in the environment.txt in the installation directory. How does programming with Voyager work? The entire Voyager functionality is encapsulated into a handful of classes. So we don’t need a precompiler or an extra utility, like we would utilize it with RMI. Usually, one would import a com.objectspace.voyager.*. Moreover you don’t have to extend (although you can if you want to) a standard Voyager class to obtain it’s functionality. Voyager provides the programmer with a few global objects (Voyager, Proxy, Agent etc) which encapsulate the entire platform functionality. Voyager does not really distinguish between local and remote objects. Instead, an object has various facets : Factory objects are the simplest and least functional type of objects. They already have the ability of performing remote transactions. They can invoke other remote objects’ methods, move to another host etc. Exported objects are Factory Objects that have been bound to a TCP port. Exported objects can receive names and thus can be accessed as remote objects from other objects. So, again they are not different in nature from Factory Objects, they just are Factory Objects with a name (more about naming conventions later on). Agents are Voyager-aware Factory Objects, meaning that somewhere inside the JAVA code an import com.objectspace.voyager will appear and that the class will make use of the corresponding abilities. From a programming point of view, each class predestinated for use as a remote object should implement it’s interface. This is not absolutely necessary, but the programming techniques for non-interface classes are somewhat more complicated. WIZE – an example In the simple demo I’ve written we’ll see various ways of constructing and working with remote objects. Nikomachos is a librarian in Alexandria. He has found that his shelves are empty and now the good man tries to find material in order to fill them. After a minor consultation with the pharaoh, he decides to ask three wise men for a quote: Heraklit of Ephesus Demokrit of Abdera Platon of Athens : “Everything flows.” : “In depth lies the truth.” : “Every science separated from justice and virtue resembles to cunning rather than wisdom.” Our librarian first visits an oracle and asks Heraklit himself (apparently by appearing in his dream) for a quote. As we will see, this corresponds to the direct invocation of a remote object’s (exported object) method. Demokritos is busy and politely declined seeing him, so Nekomachos will have the pharaoh’s elite squad abduct Demokritos. This brutal method corresponds to an attempt to relocate a remote object that has not been created by the process attempting to perform the move. After all these preparation Nekomachos has no time left to visit Plato himself, hence he sends the light-footed Thrasybulos instead to Athens. This would be the last and most complicated way, doing business via a mobile agent. For the following discussion I propose to have handy a print-out (or another window for the online fanatics among us) of the code. Installing and running Wize The demo is written for usage on a single system. However it shouldn’t be difficult to adjust the MS-DOS batch files for usage on a network, just change the corresponding parameters. Unzip the contents of the attachment into an empty directory. Change into that directory and compile the contents : javac *.java. It didn’t work? Did you include all necessary libraries in the CLASSPATH? Now run town.bat . Three DOS windows will open, each containing a Java server process. After they have initialized, start the test application by running lib.bat . Keep in mind that only the test application terminates (under normal conditions), not the other three server applications. They must be terminated manually. The servers (Town) expect four command line parameters: The town name, the town’s philosopher, a quote (included inside “…”) and a TCP port number. You can start manually various towns on different machines connected to the same network. The test application expects three parameters which represent the network address of each one of the three servers. An address forms like : //machine_name:port/Philosophers_name_who_lives_in_town Philosopher.java – IPhilosopher.java From the first two lines already one can tell: this class is not Voyager-aware. It does not implement any networking functionality. It represents a philosopher, who has a name, a town he lives in… and he knows a quote. Unlike the multi-talented ancient paradigm, our simple philosopher of 1’s and 0’s can only print his name out to the screen and return a quote – but that shall suffice for now. We will encounter ‘Philosopher’ mainly as a Factory or Exported object. Town.java The Town class has a simple function: it provides the Voyager framework to Philosopher : - By starting up the platform : Voyager.startup() - By creating an instance of Philosopher and binding it to a TCP port IPhilosopher ph=(IPhilosopher) Proxy.export(new Philosopher (quote, name, town),port); - By giving it a name through which it can be recognized and accessed by other remote objects : Namespace.bind(name,ph); A few remarks: nearly every method of Voyager classes throw exceptions, which should or even must be caught. The Town class execution will not terminate under normal conditions, as no Voyager.shutdown() method is invoked. The Philosopher object remains bound to the specified TCP port for ever… It is possible to create and export an object directly on a remote host by issuing a : Factory.create(“MyObject”, ”//remote_host:port”) The Town class has no interface (ITown) because a town serves here just the purpose of exporting a philosopher. A Town is not a remote object and thus does not qualify as anything of the three object categories. Library.java Again, a class without an interface. And indeed, it is just a server which starts and hosts an agent while interacting at the same time for demonstration purposes with ‘Philosopher’ objects that have been exported by ‘Town’s. As usual, first thing is to make sure the Voyager platform has been started : Voyager.startup(). The program will attempt to interact with three remote objects. Each of these objects must be Exported Objects – how would it be possible to access objects that are not exported? Naturally, when trying to access an object and invoke it’s methods one has to know what kind of object it is – so the least we can do is import it’s interface: import Philosopher; Next we will link up to a remote object : IPhilosopher ph1=(IPhilosopher) Namespace.lookup(town1); Notice that when accessing a remote object the class-body is not required, just it’s interface. From now on, all public methods can be accessed as if the object would reside on the local machine. Only that it doesn’t. This allows us to perform difficult and time consuming calculations on remote computers. Although not demonstrated here, one can pass parameters to the invoked methods as usual. Well, almost as usual, because any basic types (int etc) do not apply to this rule - the object variant must be used instead. In the second section I demonstrate the functionality of the Mobility object. By creating a mobility facet of an object, it is possible to move it around the network. The only reason why this attempt ends in a disgraceful exception thrown by mobility.moveTo("//localhost:3000"); is that I tried to move a remote object created by somebody else. The Library process did not create the Philosopher yet is trying to move it to it’s local computer – a Town class created and exported the Philosopher on another computer … and another process. Luckily it is impossible to remove a remote object one does not own from another process. The only way to obtain ownership over that object is to persuade the owner-process to loosen it’s grip on the object. In other words, the owner process has to move the object away and free any references to it. In the third section, a mobile agent is created and sent to the town (location) of a philosopher. The agent is created in the usual way as an exported object : IMessenger Thrasybulos=(IMessenger) Proxy.export (new Messenger("Thrasybulos")); Only the statement : Agent.of(Thrasybulos).moveTo(ph3,"atTown"); exhibits the ‘mobile’ nature of the object. Again, Thrasybulos remains an object, we just ‘look’ at it as if it was an agent, which is demonstrated pretty nicely with the Agent.of() construct. The entire code + data is transferred over the network to the location of ph3 (which is an exported Philosopher). Thrasybulos has thus left the localities of our server. What does the “atTown” string do? It instructs the Agent object to invoke the Thrasybulos.atTown method once the transfer is complete and Thrasybulos has reached his destination. This way an agent can resume execution after the journey. There are more powerful ways of working with agents by extending the Agent class and overriding it’s methods, as described in the manual. Although a copy of the object still exists on the local machine, it is not updated synchronously. This means, that one should not access this object anymore, until the agent has returned home. Should implies a form of moral obligation, but as there are no limits for programmers and other immoral elements, we’ll have now a look at what can be done with an agent’s lifeless hull as long as it’s spiritual incarnation travels through the network. One must imagine the whole process like this: After Thrasybulos has left the library, a copy of his remains there. That copy is completely autonomous until Thrasybulos returns. The very moment he returns, the copy is replaced by the repatriated object. So don’t wonder if suddenly, after you have changed some values in a departed object they change again :) Should Thrasybulos die on the way (network error, server crash etc) and news of his death reaches the library, the copy will be claimed by the garbage collector instantly. So a try {} … catch {} pair is quite suitable here. This is the reason why I decided to periodically poll the value of Thrasybulos.hasReturned. The class’ constructor defaults this value to false. However, the agent is programmed to set it to true on the event of his return. Thus, the only way this method is every going to return a true value is in case of the agent’s return. Nothing more, nothing less. A small note : Thread.sleep(1) makes sure that we don’t waste too much processing time during the poll. Messenger.java – IMessenger.java As you will probably remember, Messenger implements a mobile agent. One good thing Voyager has is that we won’t have to extend a predefined class - which increases flexibility and leads to clean code. A quick glance at Library.java will be helpful at this point. BackHome() is called from outside, usually as the result of a successful Agent.of(this).moveTo(…). After writing some info on the screen it sets the backInTown property to true, signalising to the library that Thrasybulos has returned. The atTown(IPhilosopher ph) method is somewhat more interesting. Again, it is called as the result of a successful moveTo() call. The parameter is a remote object of the town’s local philosopher. As usual, ph’s methods are accessible from here. However, the fact that the agent runs now at the same machine on the same platform with a philosopher does not allow it to access ph’s private properties or even force it to relocate… After having spoken to the philosopher, Thrasybulos sails for Alexandria. The Agent.of(this).getHome() method is quite handy at this point as it contains a string representation of the home address. With a single line of code : Agent.of(this).moveTo(Agent.of(this).getHome(),"backHome"); the agent has returned to the waiting library. Upon arrival, the agent’s backHome() method is called, as discussed earlier. Performance issues Voyager is neither a weight watcher nor a sprinter. According to Objectspace, the developers focused on flexibility, security and minimized network transfer which might pay off during the development phase. Either way, the Wize demo terminates on a local 450 MHz PC in 4 seconds (loading times not included) which is not overwhelming. [1] Java-based Agent Development Toolkits: A Survey Thomi Pilioura, Isambo Karali, Michalis Hatzopoulos