This is an example of using Object Design's PSE database with ObjectSpace's Voyager
Example PSE Pro 1.1 and Voyager 1.0b2.1 jdk 1.1.2 Solaris 2.5.1 for Sparc 3 July 1997 BlueDot Software, San Francisco, CA BlueDot Software-------------------------------------------------------------------- Continuing my research on using PSE with various RPC schemes (I use RPC schemes as the generic term for things like RMI, CORBA and now Voyager. If there is a better or more widely used term, let me know.) Here is a complete example of PSE and Voyager. Voyager is a new "RPC scheme" developed by ObjeceSpace. Readers of this newsgroup may remember that ObjectSpace is the company that makes JGL. ObjectStore distributes djgl 1.0 which is the persistent version of JGL 1.0. (We eagerly await djgl 2.0!) More about Voyager can be found at http://www.objectspace.com/voyager including a white paper comparing Voyager to RMI. (http://www.objectspace.com/voyager/voyager_white_papers.html) Unfortunately these papers are in PDF format, not HTML, but worth reading. Voyager purports to be a solution for efficient agent based systems and works nicely in non-agent situations too as an RMI replacement. I found Voyager relatively easy to work with. Of course I had already gotten through the RMI learning curve, so much of the brain work was already done. Voyager is easier to deal with than RMI. Not to be overlooked is that with Voyager you don't have to constantly be typing anything silly like "throws java.rmi.RemoteException" every time you want to do something. (I thought I was going to strangle someone over that if I kept using RMI.) Voyager claims to be much faster than RMI and, although I haven't tested this, I do know that RMI is pretty slow. I think Voyager's speed advantage comes when you make an autonomous agent and turn him loose on the world. The agent connects to the VM on a remote server and thus all communications with the remote server are done in a "same VM" environment. Hence they are much faster. In my case, I'm not making an agent (my example code is below) I am simply sending data from a client to a server, so I don't know if the speed improvement will be that great. Again, I haven't done comparison testing - I have a job to do too. Perhaps most importantly, Voyager is freely available and can be freely redistributed as part of an application (with certain exceptions noted on their license agreement.) The bottom line of my evaluation is that if ObjectSpace can tell me a good story about how adopting Voyager now won't leave me high and dry later if I need to get to full CORBA compliance, or at least get a subset of my system to CORBA compliance, I will use Voyager over RMI. (Now that I think of it, I have no idea what RMI's story is either) Of course this is also subject to my learning more about all of these things. I just starting learning all of this stuff with RMI about two months ago, so take my comments with a carton of Mortons. Voyager is easier to use than RMI (Really Muddled Interface), it offers much more flexibility/options/features than RMI (although I haven't used them yet) and it is probably faster in many cases. ObjectSpace has proven themselves in my mind with their release of JGL and its wide adoption means that I am comfortable trying more of their stuff in my upcoming products. Even in beta Voyager comes with a couple hundred pages of documentation, which is not to be interpreted as Voyager NEEDS that much documentation. It has a lot more features than RMI and they are worth reading about. Here goes, ------------------------------------------------------------------ YASE (Yet Another Store Example) ------------------------------------------------------------------ My example is a basic store application. There is a Store Server application and a Store Client application. The Store Server fires up and creates one product for itself (bananas) and then it waits for client connections. The Store Client application fires up and it creates a product (apples). The Client connects to the Store Server and sends over its product information. The Store Server adds apples to its list of products and saves them persistently. One, perhaps, unituitive aspect to my demo, is that the Client and the Server BOTH HAVE THEIR OWN PSE DATABASE. The idea is this. In a more complex application, the Store Client might be a whole set of products each of which has very detailed information for one company. Many companies may use the Store Client to send their product information to the Store Server. Each of the companies may also want to keep a local copy of their data so that they can edit their data offline and upload it to the server when convenient. The Server, of course, maintains all the product data from all the companies in its master database. So here is my code. Note that I am using packages. I know my package names are screwy but who cares. I mostly wanted a package demo because 99% of all demos in java don't use packages and the first thing I want to do after I understand the technology is to put the different parts into different packages. This always invovles a few compiler and run time headaches. So my example uses packages from the get go to save you the trouble. If something doesn't work for you, let me know. I probably don't want to spend much MORE time on this demo though. Also, if you have a Microsoft OS and you can't translate from UNIX, or you can't get this to work with Visual-Whatever, please don't ask me, but I may be asking you later. For this demo I used such advanced tools as the standard Sun jdk1.1.2 with command line javac to compile and emacs to write my code. Viva emacs! Store.java --------------------------- package Examples.VOYAGER_PSE; import COM.odi.util.*; public class Store { protected String mName; protected OSVector mProducts; public Store(String name) { mName = new String(name); mProducts = new OSVector(5); } // accessors public String name() { return mName; } public OSVector products() { return mProducts; } public void addProduct(Product p) { mProducts.addElement(p); } public Product product(int i) { return (Product)mProducts.elementAt(i); } // other methods public void print() { System.out.println("Name = " + mName); } } Product.java ------------------ package Examples.VOYAGER_PSE; public class Product { protected String mName; protected float mPrice; public Product(String name, float price) { mName = new String(name); mPrice = price; } // accessors public String name() { return mName; } public float price() { return mPrice; } // other methods public void print() { System.out.println("Name = " + mName + " : Price = " + mPrice); } } The above two classes represent all the data that will get saved persistently. In the real world the Store would be more than just a list of products and a name, but you get the idea. The Product would be more than just a name and price, but you get the idea here too. The next class is for the database itself. I want an object that connects to my database and handles all those issues for me. Here it is. PSESingleton.java //////////////////////////////////////////////////////////////////////// // File: PSESingleton.java // // Description: // This file contains PSESingleton, a class that encapsulates the // interface to an ObjectStore PSE objectbase. This class uses the // Singleton pattern to prevent multiple instances. // //////////////////////////////////////////////////////////////////////// package Examples.VOYAGER_PSE; import java.util.*; import COM.odi.*; import COM.odi.util.*; public class PSESingleton { protected String mDBName; protected static Thread mInitialThread; protected Database mDB; protected static boolean mInitialized = false; ////////////////////////////////////////////////////////////////////// // PSESingleton(String) // // Full constructor. Should be called only once to initialize // database connection. ////////////////////////////////////////////////////////////////////// public PSESingleton(String dbName) { super(); // called anyway, made explicit as a reminder if (!mInitialized) { ObjectStore.initialize(null,null); mDBName = dbName; mInitialThread = Thread.currentThread(); try { mDB = Database.open(dbName, ObjectStore.OPEN_UPDATE); } catch (DatabaseNotFoundException e) { mDB = Database.create(dbName, ObjectStore.ALL_READ | ObjectStore.ALL_WRITE); } mInitialized = true; } } // end PSESingleton(String,String,String,String) ////////////////////////////////////////////////////////////////////// // PSESingleton() // // Non-initial constructor. ////////////////////////////////////////////////////////////////////// public PSESingleton() throws PSESingletonNotInitializedException { super(); // called anyway, made explicit as a reminder if (!mInitialized) throw new PSESingletonNotInitializedException( "Singleton not initialized" ); } // end PSESingleton::PSESingleton() public void joinInitialThread() { try { ObjectStore.initialize(mInitialThread); } catch (ObjectStoreException ose) { System.out.println("ERROR: Can't connect to initial thread"); ose.printStackTrace(); } // already initialized } public void close() { joinInitialThread(); mDB.close(); mInitialized = false; ObjectStore.shutdown(false); } // accessor methods public Database database() { return mDB; } public boolean isInitialized() { return mInitialized; } }; // end PSESingleton The PSESingleton may throw an exception if it used without being initialized. Here it is with an incredibly long and ugly name. PSESingletonNotInitialized.java ----------------------------- package Examples.VOYAGER_PSE; import COM.odi.*; class PSESingletonNotInitializedException extends Exception { public PSESingletonNotInitializedException() { super(); } public PSESingletonNotInitializedException(String s) { super(s); } } // end PSESingletonNotInitializedException Now, we make the Store Server application. It is rather straightforward and there is absolutely no thought of Voyager in it, except that we have a method "receiveData" which we know will somehow be our means of collection data from the client. Note this is in its own package Examples.VOYAGER_PSE.StoreServer and is called TestServer, so don't confuse StoreServer with TestServer. TestServer.java ---------------- //////////////////////////////////////////////////////////////////////// // File: TestServer.java // Date: 5-May-97 // Prog: Gene McKenna // // Description: // A simple program to test Voayger with PSE Pro 1.1 // // Sample Usage: // // To Do: // // Revision History: // date prog comments // --------- ---- -------------------------------------------------- //////////////////////////////////////////////////////////////////////// package Examples.VOYAGER_PSE.StoreServer; import Examples.VOYAGER_PSE.*; import java.util.*; import java.io.Serializable; import COM.odi.*; import COM.odi.util.*; import COM.odi.ObjectStore.*; import COM.odi.util.*; public class TestServer implements java.io.Serializable { // The server data for the TestServer protected Store mStore; // The things PSE needs protected PSESingleton mPSE; protected Database mDB; protected Transaction mTransaction; protected String mDBName; // default constructor public TestServer() { mDBName = "ServerDataBase.odb"; // initialize PSE ostoreInit(mDBName); // update the database and leave a transaction open update(); // now wait for calls from the client } // end TestServer() private void ostoreInit(String dbName) { mPSE = new PSESingleton(dbName); mDB = mPSE.database(); mTransaction = Transaction.begin(ObjectStore.UPDATE); try { mStore = (Store)mDB.getRoot("root"); System.out.println("TestServer: database found. It contains: "); print(); } catch (DatabaseRootNotFoundException drnfe) { mStore = new Store("Safeway"); mStore.addProduct(new Product("Bananas", (float)0.49)); mDB.createRoot("root", mStore); System.out.println("TestServer: New Store created"); print(); } mTransaction.commit(); } // end ostoreInit() // // At start-up this is called to read in the database and make any // initial changes. // // The transaction opened here is closed only on save() or abort(). // public void update() { mPSE.joinInitialThread(); // read in the data mTransaction = Transaction.begin(ObjectStore.UPDATE); mStore = (Store)mDB.getRoot("root"); // leave the transaction open } // end update() public void receiveData(Product p) { System.out.println("inside receiveData"); mPSE.joinInitialThread(); mStore.addProduct(p); System.out.println("TestServer: Store Server now contains: "); print(); System.out.println("TestServer: saving to ServerDatabase"); save(); System.out.println("TestServer: saved"); } protected void save() { mPSE.joinInitialThread(); int dbSize = mDB.getSizeInBytes(); System.out.println("TestServer: pre-save size " + dbSize); System.out.println("TestServer: saving values: "); print(); mTransaction.commit(ObjectStore.RETAIN_UPDATE); mTransaction = Transaction.begin(ObjectStore.UPDATE); dbSize = mDB.getSizeInBytes(); System.out.println("TestServer: post-save size " + dbSize); } protected boolean saveFinal() { mPSE.joinInitialThread(); System.out.println("TestServer: saving values: "); print(); mTransaction.commit(); System.out.println("TestServer: Shutting down ObjectStore"); mPSE.close(); return true; } public void print() { mStore.print(); for (int i=0; i <mStore.products().size(); i++) { Product e = (Product)mStore.product(i); e.print(); } } public static void main(String args[]) { TestServer srvr; try { srvr = new TestServer(); } catch (Exception e) { System.out.println("ERROR: Exception " + e); } } // end main(String[]) } // end class TestServer Notice that when the TestServer starts up it either loads the existing database "ServerDataBase" or it creates a new one. If it creates a new one it automatically adds the product "Bananas" and saves it via PSE. This is basically a "Hey, lets make sure this database thing is working" test. Then it sits and waits for more data via its "receiveData" method. Here's where Voyager comes in. To accomplish the inter-application communication you must create a Virtual TestServer, that is a virtual copy of the TestServer. This is done rather easily via Voyager's "vcc" and is pretty much like making a Stub/Skeleton pair in RMI, only there is just a stub here. Make this Stub via vcc -p Examples.VOYAGER_PSE.StoreServer TestServer (Don't worry I have a complete build script included too) The above creates a file called VTestServer.java. Next, we need to create our registry. RMI also has a registry "thingy". You build the Voyager Registry yourself. Here is mine. Note, again I have switched to a new package. Examples.VOYAGER_PSE.Registry Build.java ------------ package Examples.VOYAGER_PSE.Registry; import Examples.VOYAGER_PSE.StoreServer.*; import COM.objectspace.voyager.core.*; public class Build { public static void main(String args[] ) { // create TestServer in local server @ port 7000 VTestServer storeServer = new VTestServer("localhost:7000"); storeServer.liveForever(); // create vector with alias "Registry" in localhost @ server port 7000 VVector registry = new VVector("localhost:7000/Registry"); registry.liveForever(); registry.addElement(storeServer); System.out.println("Registry is " + registry); Voyager.shutdown(); } } If you are paying attention, you should be asking "What in the hell is a VVector?". The answer is not that it is part of COM.objectspace.voyager.core. VVector is a virtual vector, just like VTestServer is a virtual TestServer. So before you can compile the above you have to make a VVector. Again , you use "vcc" vcc -p Examples.VOYAGER_PSE.Registry java.util.Vector Now the last step is to make the Store Client. Remember, my Store Client has its own PSE database as well. I explained above why you might want this. If you don't want it, you can probably rip it out, but I want it so I'm not gonna do it for you. :) Again, I'm switching packages because that makes sense. The package is Examples.VOYAGER_PSE.StoreClient and the application is TestClient. Don't be confused. TestClient.java --------------- //////////////////////////////////////////////////////////////////////// // File: StoreClient.java //////////////////////////////////////////////////////////////////////// package Examples.VOYAGER_PSE.StoreClient; import Examples.VOYAGER_PSE.*; import Examples.VOYAGER_PSE.Registry.*; import Examples.VOYAGER_PSE.StoreServer.*; import java.util.*; import COM.objectspace.voyager.core.*; import COM.odi.*; import COM.odi.util.*; public class TestClient { // The local data for this TestClient protected Product mProduct; // A connection to the TestServer application protected VTestServer mServer; // The things PSE needs protected PSESingleton mPSE; protected Database mDB; protected Transaction mTransaction; protected String mDBName; public TestClient() { mProduct = new Product("Apples", (float)0.89); mDBName = "MyDataBase.odb"; // initialize PSE ostoreInit(mDBName); // update the datbase based on any startup values update(); // send the current data to the server and update it based // on what the server sends back try { register(); } catch (Exception e) { System.out.println("exception " + e); } // write the returned data to the database save(); } // end TestClient private void ostoreInit(String dbName) { mPSE = new PSESingleton(dbName); mDB = mPSE.database(); mTransaction = Transaction.begin(ObjectStore.UPDATE); try { mProduct = (Product)mDB.getRoot("root"); System.out.println("TestClient: database found. It contains: "); mProduct.print(); } catch (DatabaseRootNotFoundException drnfe) { mDB.createRoot("root", mProduct); System.out.println("TestClient: New Database created"); mProduct.print(); } mTransaction.commit(); } // end ostoreInit() // // At start-up this is called to read in the database and make any // initial changes. // // The transaction opened here is closed only on save() or abort(). // public void update() { mPSE.joinInitialThread(); // read in the data mTransaction = Transaction.begin(ObjectStore.UPDATE); mProduct = (Product)mDB.getRoot("root"); // make a change right away just to say we did. System.out.println("TestClient: data object now contains: "); mProduct.print(); // leave the transaction open } // end update() protected boolean connectServer() { try { System.out.println("Connection to server"); VVector registry; registry = (VVector)VObject.forObjectAt("localhost:7000/Registry"); mServer = (VTestServer)registry.elementAt(0); } catch (Exception e) { System.out.println("Error connecting to server. " + e.getMessage()); e.printStackTrace(); return false; } return true; } protected void register() { mPSE.joinInitialThread(); if (connectServer()) { System.out.println("TestClient: Sending this to the Server"); mProduct.print(); mServer.receiveData(mProduct); } // a good idea to save right after we send to the server // that way the server's data and the persistant data here // are synched. save(); } // end register() protected void save() { mPSE.joinInitialThread(); System.out.println("TestClient: saving values: "); mTransaction.commit(ObjectStore.RETAIN_UPDATE); mTransaction = Transaction.begin(ObjectStore.UPDATE); } public static void main(String args[]) throws java.io.IOException, java.io.FileNotFoundException { TestClient client; if (args.length == 0) { client = new TestClient(); } System.exit(1); } // end class TestClient } If you followed the database code in TestServer, you will notice that it is pretty much the same here. Note that we use a VTestServer and not a TestServer here. The reason of course is that it is only the VTestServer which has this magically network communication stuff. Note also that when I create my instance of the VTestServer I use a constructor that doesn't actually exist in TestServer. The TestServer constructor took no arguments, the VTestServer constructor takes one. (It always takes one more than the real object it is based on) This happens via the vcc compiler. The new field is just a network address: host and port number. VTestServer storeServer = new VTestServer("localhost:7000"); Ok now we are ready to test it. I apologize in advance that my output is ugly. Hey, I'm not writing a book here! This is my build script. This build script will completely rebuild the hell out of everything. I like to do that, especially when I am testing new technologies because it takes me awhile to understand what depends on what, who kills what, etc. I've also had trouble with javac figuring out dependencies correctly especially in the context of PSE. Anyone else come across this problem? build ------------- #!/usr/bin/sh echo "rm *.class" rm *.class echo "rm ServerDataBase.od*" rm ServerDataBase.od* echo "rm MyData.od*" rm MyData.od* echo "javac -deprecation Product.java PSESingleton.java PSESingletonNotInitializ edException.java Store.java" javac -deprecation Product.java PSESingleton.java PSESingletonNotInitializedExce ption.java Store.java echo "cd StoreServer" cd StoreServer echo "rm *.class" rm *.class echo "javac TestServer.java" javac TestServer.java echo "vcc -p Examples.VOYAGER_PSE.StoreServer TestServer" vcc -p Examples.VOYAGER_PSE.StoreServer TestServer echo "cd ../Registry" cd ../Registry echo "rm *.class" rm *.class echo "vcc -p Examples.VOYAGER_PSE.Registry java.util.Vector" vcc -p Examples.VOYAGER_PSE.Registry java.util.Vector echo "javac Build.java" javac Build.java echo "cd ../StoreClient" cd ../StoreClient echo "rm *.class" rm *.class echo "javac TestClient.java"; javac TestClient.java echo "cd .." cd .. echo "osjcfp -dest PSE Store.class Product.class" osjcfp -dest PSE Store.class Product.class echo "mv PSE/Examples/VOYAGER_PSE/* ." mv PSE/Examples/VOYAGER_PSE/* . echo "build complete" echo "" --------------------------------- Note that the PSE files, which HAVE to be placed in a different directory are very quickly moved back to the current directory. Like I said it deletes everything, including both the ServerDataBase and the Client database (MyDataBase) so remember that later when you wonder why your data disappeared between builds. Now to run it. Start up voyager, specifying its port number > voyager 7000 & voyager(tm) 1.0 (beta 2.1), copyright objectspace 1997 address = 209.0.42.16:7000, root = / Enter the registry information > java Examples.VOYAGER_PSE.Registry.Build voyager(tm) 1.0 (beta 2.1), copyright objectspace 1997 address = 209.0.42.16:54063, root = / TestServer: New Store created Name = Safeway Name = Bananas : Price = 0.49 Registry is [Examples.VOYAGER_PSE.StoreServer.TestServer@1dc6455f] These three lines: TestServer: New Store created Name = Safeway Name = Bananas : Price = 0.49 indicate that your Server has created a new database, it has created a store called "Safeway" and it has added a Product called "Bananas". I know the output could be prettier and I could use something other than "Name = " as a header for both Store name and Product name, but hey, you're getting this free. Penultimately (that means second to last), we can run the client. > java Examples.VOYAGER_PSE.StoreClient.TestClient TestClient: New Database created Name = Apples : Price = 0.89 TestClient: data object now contains: Name = Apples : Price = 0.89 Connection to server voyager(tm) 1.0 (beta 2.1), copyright objectspace 1997 address = 209.0.42.16:54065, root = / TestClient: Sending this to the Server Name = Apples : Price = 0.89 inside receiveData TestServer: Store Server now contains: Name = Safeway Name = Bananas : Price = 0.49 Name = Apples : Price = 0.89 TestServer: saving to ServerDatabase TestServer: pre-save size 2676 TestServer: saving values: Name = Safeway Name = Bananas : Price = 0.49 Name = Apples : Price = 0.89 TestServer: post-save size 2676 TestServer: saved TestClient: saving values: TestClient: saving values: Ok tons of ugly output from both Server and Client and Voyager. A smart idea might be to run all three in different windows, but I'll leave that up to your imagination. Basically the above says this. TestClient: New Database created Name = Apples : Price = 0.89 Created a new database and put Apples in it. TestClient: data object now contains: Name = Apples : Price = 0.89 The database saved and now we are verifying its contents. Connection to server voyager(tm) 1.0 (beta 2.1), copyright objectspace 1997 address = 209.0.42.16:54065, root = / TestClient: Sending this to the Server Name = Apples : Price = 0.89 We connected to the Server and reminded our listeneer just what it was that we wanted to send to the Server. inside receiveData This is the Server talking. We made it into the receiveData method. TestServer: Store Server now contains: Name = Safeway Name = Bananas : Price = 0.49 Name = Apples : Price = 0.89 After we add the new Product, what do we have? TestServer: saving to ServerDatabase TestServer: pre-save size 2676 We're gonna commit it to the database, note the size of the database before the save. TestServer: saving values: Name = Safeway Name = Bananas : Price = 0.49 Name = Apples : Price = 0.89 TestServer: post-save size 2676 TestServer: saved It is now saved in the Server database. Note that the database size is still the same. This is because PSE probably doesn't add more space for a Vector every time it needs to add just one, it adds a bunch of space, and when that gets full, it adds a bunch more. Just a guess. TestClient: saving values: TestClient: saving values: Now the client saves twice. I forget why it saves twice, but who cares. Finally (this means the last thing) if you want to verify that the Server's database actually saved something, rerun the client. This time your server should report that it has Bananas and then Apples and then more Apples.
Return to Gene's Home Page
Return to Gene's Random Java Crap