Javanaise: installation manual, user's manual

Javanaise is a service handling distributed shared objects on top of Java/RMI. It aims at improving performance by caching local duplicates of the shared objects. It has a three-layered architecture: Javanaise clients talk with their local servers, which in turn exchange information (mostly locking information) with a global coordinator.

Requirements

In order to run a Javanaise-enabled application, you need a Java Virtual Machine on each machine you want to run a Javanaise Client, Server or Coordinator. The machine that hosts the Javanaise Coordinator also needs a Java RMI Registry running locally.

In order to create Javanaise-enabled applications, you'll need a Java compiler compliant with Java 5 or higher (since because Javanaise-enabled applications require annotations), and the Apache Velocity jars in your classpath.

This tutorial assumes you know how to compile and run Java programs in the command line, or in an IDE such as Eclipse. We used both the command line and Eclipse to develop and test our implementation of Javanaise.


Installation

There's no real installation for Javanaise: to use Javanaise you only have to put the JAR in the classpath of your project. The exact way to do that is beyond the scope of this manual, and it depends on whether you're using javac, an ant buildfile, a Java project in an IDE such as Eclipse, etc.

In the distribution, in addition to complete sources, an ant buildfile and an Eclipse project is provided for you to play with the examples.


Usage

There are two ways of using Javanaise:
  1. Using the Javanaise API with generic interceptor objects:
    JvnServer js = JvnServer.jvnGetServer(); // Get the local Javanaise server, that talks to the coordinator.
    // C1 is your business class, that will be accessed through the Javanaise object.
    // Check if the shared object already exists.
    JvnObject jo = js.jvnLookupObject("<shared object name>",C1.class);
    // NOTE: "C1.class" is used for type checking, to avoid
    // trouble in rare cases.
    if (jo == null) {
        // If it doesn't exist, create one.
        // C1 must implement interface java.io.Serializable !
        jo = js.jvnCreateObject(new C1(<params>));
        // Register the object in the Javanaise system.
        js.jvnRegisterObject("<shared object name>", jo);
        // After creation, you have a write lock on the object.
        jo.jvnUnLock();
    }
    
    // Now you can use the Javanaise object.
    
    // Request lock on the object (synchronous operation).
    jo.jvnLockRead(); // or jo.jvnLockWrite();
    // You can access the object passed to JvnObject.jvnCreateObject using
    // JvnObject.jvnGetObjectState, so the following line will call a method.
    // Note that such method calls must be made between a Lock/Unlock pair.
    jo.jvnGetObjectState().<method>(<params>);
    jo.jvnUnLock(); // Release the lock on the object.
    

    You have to take care yourself of not creating interlocks or deadlocks, and you must not use the object contained in the Javanaise object (the object of type C1 in the example above).

    Let's make a concrete example: simple chat clients (Irc) exchange text (a single, shared Sentence object). Pressing the buttons enables writing and reading the sentence, and manipulating the locks. The client is multithreaded, because the lock operations are blocking, and the interface can deadlock if the program isn't multithreaded.

    irc/Sentence.java:
    package irc;
    
    import jvn.JvnRead;
    
    /**
     * Sentence class : used for representing the text exchanged between users
     * during a chat application
     *
     * @author Fabienne BOYER
     * @author Lionel DEBROUX, http://lionel.debroux.free.fr
     * @author Savas Ali TOKMEN, http://ali.tokmen.com
     */
    
    
    public class Sentence implements java.io.Serializable {
        private static final long serialVersionUID = -5744335023076980519L;
        String data;
    
        public Sentence() {
            data = new String("");
        }
        
        @JvnWrite public void write(String text) {
            data = text;
        }
        @JvnRead public String read() {
            return data;
        }
        
    }
    
    irc/Irc.java:
    package irc;
    
    /***
     * Irc class : simple implementation of a chat using JAVANAISE
     *
     * @author Fabienne BOYER,
     * @author Lionel DEBROUX, http://lionel.debroux.free.fr
     * @author Savas Ali TOKMEN, http://ali.tokmen.com
     */
    
    import java.awt.*;
    import java.awt.event.*;
    
    import jvn.*;
    
    
    /**
     * This class is a simple demonstration of the Javanaise system. It defines an
     * IRC system, where people can read and write messages. Of course, messages are
     * stored as {@link JvnObject}s and use the Javanaise protocol.
     */
    public class Irc {
        protected TextArea text;
        protected TextField data;
        protected JvnObject sentence;
    
        /**
         * Main method: creates a JVN object named IRC for representing the IRC application
         **/
        public static void main(String argv[]) {
            try {
                // initialize JVN
                JvnServerImpl js = JvnServerImpl.jvnGetServer();
    
                if (js == null) {
                    System.out.println("IRC problem : cannot create server!");
                }
                else {
                    // look up the IRC object in the JVN server
                    // if not found, create it, and register it in the JVN server
                    JvnObject jo = js.jvnLookupObject("IRC",JvnObject.class);
                    if (jo == null) {
                        jo = js.jvnCreateObject(new Sentence());
                        // after creation, I have a write lock on the object
                        jo.jvnUnLock();
                        js.jvnRegisterObject("IRC", jo);
                    }
                    // create the graphical part of the Chat application
                    new Irc(jo);
                }
            } catch (JvnException je) {
                System.out.println("IRC problem : " + je.getMessage());
            }
        }
    
        /**
         * Constructor: creates the frame and the frame's contents.
         *
         * @param jo the JVN object representing the Chat
         **/
        public Irc(JvnObject jo) {
            // Create frame
            sentence = jo;
            Frame frame=new Frame("Javanaise Client: IRC");
            frame.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    e.getWindow().dispose();
                    try {
                        JvnServerImpl.jvnGetServer().jvnTerminate();
                    } catch (JvnException je) {}
                    System.exit(0);
                }
            });
            frame.setLayout(new BorderLayout(1,1));
    
            // Create and add texts
            Panel texts = new Panel(new GridLayout(1,1));
            text=new TextArea(10,60);
            text.setEditable(false);
            text.setForeground(Color.red);
            text.setBackground(Color.black);
            texts.add(text);
            data=new TextField(40);
            texts.add(data);
    
            // Create and add buttons
            Panel buttons = new Panel(new GridLayout(1,1));
            Button read_and_unlock_button = new Button("Read + unlock");
            read_and_unlock_button.addActionListener(new ButtonListenerJvn1(this, ButtonType.ReadAndUnlock));
            buttons.add(read_and_unlock_button);
            Button read_button = new Button("Read");
            read_button.addActionListener(new ButtonListenerJvn1(this, ButtonType.Read));
            buttons.add(read_button);
            Button unlock_button = new Button("Unlock");
            unlock_button.addActionListener(new ButtonListenerJvn1(this, ButtonType.Unlock));
            buttons.add(unlock_button);
            Button write_button = new Button("Write");
            write_button.addActionListener(new ButtonListenerJvn1(this, ButtonType.Write));
            buttons.add(write_button);
            Button write_and_unlock_button = new Button("Write + unlock");
            write_and_unlock_button.addActionListener(new ButtonListenerJvn1(this, ButtonType.WriteAndUnlock));
            buttons.add(write_and_unlock_button);
    
            // Add texts + buttons and display frame
            frame.add(texts, BorderLayout.CENTER);
            frame.add(buttons, BorderLayout.SOUTH);
            frame.setSize(500,200);
            frame.setVisible(true);
        }
    }
    
    
    /**
     * Internal class to listen to button actions.
     */
    class ButtonListenerJvn1 implements ActionListener {
        Irc irc;
        ButtonType button;
    
        /**
         * Initializes the button listener object.
         * 
         * @param i Irc class associated with the button.
         * @param b Button type.
         */
        public ButtonListenerJvn1 (Irc i, ButtonType b) {
            irc = i;
            button = b;
        }
    
        /**
         * Management of user events
         * 
         * @see ActionListener#actionPerformed(ActionEvent)
         */
        public void actionPerformed (ActionEvent e) {
            (new ButtonThreadJvn1(irc, button)).start();
        }
    }
    
    /**
     * This class defines a thread which is activated when any button is pressed.
     * We do this using threads in order not to block the interface when the lock
     * is being processed and let the user press any button at any time.
     */
    class ButtonThreadJvn1 extends Thread {
        private Irc irc;
        private ButtonType button;
    
        /**
         * Constructor: initialized the ButtonThread.
         *
         * @param i IRC class to use.
         * @param b Which button has been pressed.
         */
        ButtonThreadJvn1( Irc i, ButtonType b ) {
            irc = i;
            button = b;
        }
    
        /**
         * Starts the thread.
         *
         * @see Thread#run()
         */
        public void run() {
            try {
                if( button == ButtonType.Read || button == ButtonType.ReadAndUnlock ) {
                    // lock the object in read mode
                    irc.sentence.jvnLockRead();
    
                    // invoke the method
                    String s = ((Sentence)(irc.sentence.jvnGetObjectState())).read();
    
                    // display the read value
                    irc.data.setText(s);
                    irc.text.append(s+"\n");
    
                    if( button == ButtonType.ReadAndUnlock ) {
                        // unlock the object
                        irc.sentence.jvnUnLock();
                    }
                } else if( button == ButtonType.Unlock ) {
                    // unlock the object
                    irc.sentence.jvnUnLock();
                } else if( button == ButtonType.Write || button == ButtonType.WriteAndUnlock ) {
                    // get the value to be written from the buffer
                    String s = irc.data.getText();
    
                    // lock the object in write mode
                    irc.sentence.jvnLockWrite();
    
                    // invoke the method
                    ((Sentence)(irc.sentence.jvnGetObjectState())).write(s);
    
                    if( button == ButtonType.WriteAndUnlock ) {
                        // unlock the object
                        irc.sentence.jvnUnLock();
                    }
                }
            } catch (JvnException je) {
                System.out.println("Javanaise error while processing IRC button action: " + je.getMessage());
            } catch( Exception e ) {
                System.out.println( "Error while processing IRC button action: "+e);
            }
        }
    }
    
    irc/ButtonType.java:
    package irc;
    
    /**
     * Button types used in Irc and IrcJvn2.
     * @author Lionel DEBROUX, http://lionel.debroux.free.fr
     * @author Savas Ali TOKMEN, http://ali.tokmen.com
     */
    public enum ButtonType {
        Read,
        ReadAndUnlock,
        Unlock,
        WriteAndUnlock,
        Write
    }
    
    After compiling the files, you have to launch, in this order, in four different terminals:
    ant rmiregistry
    ant coordinator
    ant irc
    ant irc
    
    This will launch two IRC windows (you can launch more by typing ant irc more times) in which you can write a message and read the latest message written.
  2. Using the Javanaise API with specific generated interceptor objects. That is, manipulating a wrapper class, with the exact same methods as your business class, instead of your business class. The wrapper class takes care of locks for you.

    In order to generate wrapper classes, you have to use the JvnWrapperCreator tool, bundled in the Javanaise jar file. This is done by using a command such as java -jar [path_to_javanaise.jar] <complete class names (without ".class")> (or its equivalent with "Run..." in Eclipse).

    We will now explain how to use the @JvnWrite and @JvnRead annotations. As an example, we'll be converting the IRC application to Javanaise 2.

    Get into the directory where you created and type $ java -jar javanaise.jar irc.Sentence. This will generate the irc/JvnSentence.java file, which simply contains delegations to the Sentence object:

    /**
     * Javanaise wrapper for Sentence class.
     */
    package irc;
    
    public class JvnSentence extends jvn.JvnObjectImpl implements java.io.Serializable {
    
        // Fields
    
        // Constructors and methods, in the following order:
        // * Constructors
        // * @JvnRead methods
        // * @JvnWrite methods
        // * other methods
    
    
        public
        JvnSentence()
        throws jvn.JvnException
        {
            super((java.io.Serializable)(new Sentence()));
        }
    
    
        @jvn.JvnRead() 
        public
        java.lang.String
        read()
        throws jvn.JvnException
        {
            if (__containedObject != null) {
                java.lang.String returnVal;
                this.jvnLockRead();
                returnVal = ((Sentence)__containedObject).read();
                this.jvnUnLock();
                return returnVal;
            }
            else {
                throw new jvn.JvnException("JvnSentence instance " + this + " not initialized !");
            }
        }
    
    
        @jvn.JvnWrite() 
        public
        void
        write(java.lang.String param0)
        throws jvn.JvnException
        {
            if (__containedObject != null) {
                this.jvnLockWrite();
                ((Sentence)__containedObject).write(param0);
                this.jvnUnLock();
            }
            else {
                throw new jvn.JvnException("JvnSentence instance " + this + " not initialized !");
            }
        }
    
    
    }
    
    Now, let's point only the important differences between the old and the new IRC client:
    $ diff -Naur src/irc/Irc.java src/irc/IrcJvn2.java
    --- src/irc/Irc.java    2006-11-25 12:34:09.000000000 +0100
    +++ src/irc/IrcJvn2.java        2006-11-25 13:35:25.000000000 +0100
    @@ -11,6 +11,8 @@
     import java.awt.*;
     import java.awt.event.*;
    
    +import irc.JvnSentence;
    +
     import jvn.*;
    
    
    @@ -19,10 +21,10 @@
      * IRC system, where people can read and write messages. Of course, messages are
      * stored as {@link JvnObject}s and use the Javanaise protocol.
      */
    -public class Irc {
    +public class IrcJvn2 {
            protected TextArea text;
            protected TextField data;
    -       protected JvnObject sentence;
    +       protected JvnSentence sentence;
    
            /**
             * Main method: creates a JVN object named IRC for representing the IRC application
    @@ -38,9 +40,9 @@
                            else {
                                    // look up the IRC object in the JVN server
                                    // if not found, create it, and register it in the JVN server
    -                               JvnObject jo = js.jvnLookupObject("IRC",JvnObject.class);
    +                               JvnSentence jo = (JvnSentence)(js.jvnLookupObject("IRC",JvnSentence.class));
                                    if (jo == null) {
    -                                       jo = js.jvnCreateObject(new Sentence());
    +                                       jo = new JvnSentence();
                                            // after creation, I have a write lock on the object
                                            jo.jvnUnLock();
                                            js.jvnRegisterObject("IRC", jo);
                                    }
    [...]
    @@ -165,38 +161,20 @@
             */
            public void run() {
                    try {
    // No more lock/unlock and typecasts.
    -                       if( button == ButtonType.Read || button == ButtonType.ReadAndUnlock ) {
    -                               // lock the object in read mode
    -                               irc.sentence.jvnLockRead();
    -
    +                       if( button == ButtonType.Read ) {
                                    // invoke the method
    -                               String s = ((Sentence)(irc.sentence.jvnGetObjectState())).read();
    +                               String s = irc.sentence.read();
    
                                    // display the read value
                                    irc.data.setText(s);
                                    irc.text.append(s+"\n");
    
    -                               if( button == ButtonType.ReadAndUnlock ) {
    -                                       // unlock the object
    -                                       irc.sentence.jvnUnLock();
    -                               }
    -                       } else if( button == ButtonType.Unlock ) {
    -                               // unlock the object
    -                               irc.sentence.jvnUnLock();
    -                       } else if( button == ButtonType.Write || button == ButtonType.WriteAndUnlock ) {
    +                       } else if( button == ButtonType.Write ) {
                                    // get the value to be written from the buffer
                                    String s = irc.data.getText();
    
    // No more lock/unlock and typecasts.
    -                               // lock the object in write mode
    -                               irc.sentence.jvnLockWrite();
    -
                                    // invoke the method
    -                               ((Sentence)(irc.sentence.jvnGetObjectState())).write(s);
    -
    -                               if( button == ButtonType.WriteAndUnlock ) {
    -                                       // unlock the object
    -                                       irc.sentence.jvnUnLock();
    -                               }
    +                               irc.sentence.write(s);
                            }
                    } catch (JvnException je) {
                            System.out.println("Javanaise error while processing IRC button action: " + je.getMessage());
    

    This point ends the Javanaise user's guide.


Future work

Javanaise could be improved by:

However, as we have significant amounts of homework in other subjects, we did not try to do those improvements (although the first item doesn't look hard and the second one is trivial).

Lionel DEBROUX and Savas Ali TOKMEN, 2006/10-11