DISTRIBUTED OBJECT TECHNOLOGIES COMPARED:

A REMOTE OBJECT PERSISTENCE MECHANISM

by

Dare Obasanjo

and

Sanjay Bhatia

Client-Server Computing: Two-Tier and Three-Tier Architectures

Client-server computing is an extension of the division of programming tasks into modular pieces which spawned object oriented programming and component programming taken to another extreme. In client-server computing, an architecture is created that allows a certain amount of work to be split amongst disparate processes on one or more machines with some means of communication between the processes being established. Typical client-server architecture consists of a “client” process which requests services and a “server” process which satisfies requests for services. The client and server processes are traditionally on different machines but can also exist as separate processes on a single machine.

Client-server programming evolved from file sharing applications where a client downloads files from a shared location and then the user jobs interacts with the downloaded file. File sharing architectures work if shared usage and the volume of data transfer is low but have difficulty scaling to a large number of users.  Such file sharing architectures were soon superseded by client-server architectures. Early client-server architectures replaced the file server with a relational database server which to replace the file server. Transaction times were improved because instead of transferring entire files every time the client made a request, the relational database management system (RDBMS) could directly answer the client’s request and only send the data that was needed. Thus providing a query response rather than complete file transfer reduced network traffic, also multiple users updating a single data file could now be handled in a more effective manner.

Unfortunately two-tier client-server architectures consisting of a user system (usually a GUI) directly communicating with a database system has difficulty dealing with a large number of users because the database serves usually maintain a separate database connection for each client. Maintaining a separate connection for each client means that the server runs out of resources once the number of connected clients reaches a certain ceiling. Also two-tier architectures often lead to vendor lock-in because clients would create specialized stored procedures that improved the performance of database queries but were not portable between database systems. The solution to this problem was the three-tier architecture where a middle-tier was placed in between the client and the RDBMS. The middle-tier provides business logic as well acting as a communication broker between the client and the server. With a three-tier system it is no longer necessary to have a single database connection for each user and instead database connections can be pooled and shared amongst the different users. Another advantage of the three-tier architecture is that it is more portable as presentation logic, business logic, and data-access logic are all kept separate. Distributed object technologies are a popular way to implement three-tier client-server architectures because they enhance the maintainability of such systems and are also interoperable between platforms and/or languages.

Description of the Remote Object Persistence Mechanism (ROPEM)

            In an attempt to learn more about distributed object systems we designed a simple system for storing objects on a remote server. Specifically there are two kinds of objects in the system a PersistableRemoteObject which is an entity that can be stored and retrieved from a remote database and a RemoteObjectFactory which can retrieve and store PersistableRemoteObjects. The PersistableRemoteObjects have their states persisted in the database as an XML string which can be loaded to and from instances of the class. Whenever a client requests an object from the server the XML string representing the saved state of the object is retrieved from the database and is then used to populate a new instance of the class which is then sent to the client. On the other hand when the client wants to store an object, it is sent to the server and the XML representation of its state is obtained and stored in the database. Below is an outline of how both classes should be structured.


PersistableRemoteObject has the following methods.

getCreationID()

Obtains the value of the creation ID for the object. When a PersistableRemoteObject is sent to a client, a unique ID is generated which is used to identify the client that instantiated the object and is currently using it.  

getObjectID()

Obtains the value of the object ID. This is a unique identifier used to locate the object in the database, it is similar to a primary key in database parlance.

  getXMLRepresentation()

Generates an XML representation of the Object. This XML

representation is what is stored in the database instead of the actual object binary.

loadFromXML(xmlString)

  Populates the data/attributes in the object from an XML string.

setCreationID(creation_id)

  Sets the value of the creation ID.

setObjectID(object_id)

Sets the value of the object ID.

The PersistableRemoteObjects are stored in a relational database table with the following metadata.

Name

Datatype

Constraints

object_id

varchar(100)

primary key

xml_representation

varchar(7250)

not null

object_type

varchar(500)

not null

Locked

Bit

not null

lock_owner_id

varchar(20)




RemoteObjectFactory has the following methods:

createObject(object_id, object_metadata):

Checks to see if the object ID is not in use and if it isn't will create a new instance of the class, which must be confirmed to be a PersistableRemoteObject, and then stores that freshly minted object in the remote store before returning it to the calling function.

isLocked(String object_id): 

Checks to see if an object is locked. The Remote Object Persistence Mechanism supports a simple transactional system where a specific client can lock an object thus preventing updates to the object’s persistent state on the server until unlocked by the client that initiated the lock. While an object is locked in can still be loaded, and thus shared, by other clients but it can only accept updates from one client.

loadObject(object_id, object_metadata)

          Obtains an object from the remote object store, if it exists.

lock( object_to_lock)

          Prevents any updates to the object until the client that initiated the lock unlocks the object.

  storeObject(PersistableRemoteObject pro)

           This stores the specified PersistableRemoteObject in the remote object store. If the object has never been stored before a new entry is created for it and if it already exists then it is updated. This method fails if the object is currently locked by another client.

unlock(object_to_unlock)

          Releases a lock placed on an object, if its creation ID matches that of the client that originally locked the object.

            With the above framework it is possible to create rich, multi-user, networked applications such as workflow software, email programs, scheduling software, group conferencing programs, group scheduling software, shareable multimedia applications, etc.  For our comparison of the various distributed object technologies we decided to implement an online jargon file (dictionary) that is viewable by several users at once who can add and modify entries stored on a remote machine.

. We chose to use standards compliant parsers for XML manipulation to take advantage of performance as well as support Document Type Definitions.  Document Type Definitions are declarative frameworks of rules that define how to validate XML.  For our word component, the XML representation of an entry in the jargon file would look like this:

<?xml version="1.0" ?>
<word name="XML">
<definition>The Extensible Markup Language</definition>
<definition>A W3C Standard</definition>
</word>

The DTD for the word component looks like this:


<!ELEMENT word (definition*)>
<!ATTLIST word name CDATA #REQUIRED>
<! ELEMENT definition CDATA #REQUIRED>

This declares the word XML contains a word tag with a name attribute inside of which are embedded one or more definition tags.  These definition tags contain no attributes, but wrap the definition data. Technologies like DTD add high-level verification to our framework which makes it more secure and reliable.  If bad data somehow got into the database or one of the data objects, the system would reject it since it would not pass DTD verification.



Comparisons of CORBA, DCOM and RMI implementations of ROPEM

CORBA implementation


            The CORBA implementation of ROPEM was implemented in Java because writing CORBA code in Java is less complex than in most other languages for which CORBA bindings exist. Below is the interface definition for the ROPEM which is described in a file called RemoteObjectStore.idl

module RemoteObjectStore{

exception XMLLoadException{};

interface PersistableRemoteObject{

//the time the object was created on the server, used to determine which

//of the object instances this one is

attribute string creationID;

//unique object identifier

attribute string objectID;

//Generates an XML representation of the Object

string getXMLRepresentation();

//Populates the data in the object from an XML string

void loadFromXML(in string xmlString) raises (XMLLoadException);

};

exception ObjectAlreadyExistsException{};

exception ObjectNotFoundException{};

exception ObjectLockedException{};

interface RemoteObjectFactory{

//Checks to see if the object ID is not in use and if it isn't will

//populate the object passed in with the data for the class

void createObject(in string objectID, inout PersistableRemoteObject pro)

  raises (ObjectAlreadyExistsException);

//checks to see if the object is locked

boolean isLocked(in string objectID) raises (ObjectNotFoundException);

//Obtains an object from the remote object store.

  void loadObject(in string objectID, inout PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);

//This stores the specified PersistableRemoteObject in the remote object store

  void storeObject(in PersistableRemoteObject pro)

    raises (ObjectLockedException);

//Prevents any updates to the object until the object is unlocked

  void lock(in PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);

//  Releases a lock placed on an object, if unlocked by the original locker

  void unlock(in PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);

       

         

};

 

};

The IDL file specifies the operations and attributes that the remote objects support as well as the exceptions raised by each operation.  Data types that an operation returns, its parameters as well as the object's attributes are also described in the IDL file. The idltojava compiler generates a number of Java files when run on the IDL file including numerous exception classes that are subclasses of org.omg.CORBA.UserException , client-side stubs and server-side skeletons of the interfaces described in the IDL , Helper classes which contain methods for manipulating IDL types such as the narrow() which is used for static typecasts, Holder classes which are used for out and inout parameters as well as the interface classes for RemoteObjectFactory and PersistableRemoteObject. It should be noted that the idltojava compiler changes attributes listed in the IDL interfaces to accessor and modifier methods in the generated Java interfaces as can be seen in the PersistableRemoteObject interface above with regards to creationID and objectID

import RemoteObjectStore.*;

import org.xml.sax.*;

import org.xml.sax.helpers.*;

import java.util.*;

import java.io.*;

public class JargonFileEntry extends _PersistableRemoteObjectImplBase

    implements DocumentHandler {

   

    /*=================================================================*/

    /*                  C L A S S      V A R I A B L E S               */

    /*=================================================================*/

  

    final private static String parserName = "com.jclark.xml.sax.Driver";

    /*=================================================================*/

    /*                 M E M B E R      V A R I A B L E S              */

    /*=================================================================*/

    /**

     * This is used for associating a SAX event with a document location.

     */

    private Locator locator=null;

  

    private String objectID=null;

    private String creationID=null;

    private ArrayList definitions = new ArrayList();

    private boolean in_data_tag = false;

     /*=================================================================*/

    /*                  C O N S T R U C T O R S                        */

    /*=================================================================*/

   

    /**

     * Default constructor.

     */

    public JargonFileEntry() {;}

   

    /*==================================================================*/

    /*                 M E M B E R       F U N C T I O N S              */

    /*=================================================================*/

  

    public void startDocument (){;}

    public void endDocument(){;}

 

    public void ignorableWhitespace(char[] ch, int start, int length){; }

        

    public void processingInstruction(java.lang.String target,

                                                                      java.lang.String data){;}

      

    public void setDocumentLocator(Locator locator){this.locator = locator;}

      

    public void startElement (String name, AttributeList atts)

    {

                //System.out.println("Start element: " + name);

               

                if(name.equals("jargon_file_entry")==true)

                    this.objectID = atts.getValue(0);

                else

                    if(name.equals("definition")==true)

                                in_data_tag = true;

    }

    public void endElement (String name){

               

                in_data_tag = false;

               

    }/* endElement(String) */

   

    public void characters(char ch[],int start, int length){

               

                if(in_data_tag == true){

                    definitions.add(new String(ch, start, length));

                }   

                               

    } /* characters(char[], int, int) */

    public String  objectID() {return objectID;}

   

    public void objectID(String   v) {this.objectID = v;}

   

    public String creationID(){ return this.creationID; };

   

    public void creationID(String  v){this.creationID = v;}

   

  

    public String getXMLRepresentation(){

                StringBuffer toReturn = new StringBuffer("<?xml version=\"1.0\"?>\n");

                //beginning tag

                toReturn.append("<jargon_file_entry word=\"");

                toReturn.append(this.objectID ==null? "" : this.objectID );         

                toReturn.append("\">\n");

                //definitions

                int num_defs = this.definitions.size();

                for(int i=0; i < num_defs; i++){

                    toReturn.append("<definition>");

                     toReturn.append(definitions.get(i).toString());

                    toReturn.append("</definition>\n");

                }

                //end tag

                toReturn.append("</jargon_file_entry>");

                return toReturn.toString();

 

    }/* getXMLRepresentation() */

    public void loadFromXML(String xmlString)throws XMLLoadException{

                try{

                    Parser parser = ParserFactory.makeParser(parserName);

                    parser.setDocumentHandler(this);

                   

                    parser.parse(new InputSource(

                                                       new CharArrayReader(xmlString.toCharArray())

                                                       ));

                    }catch(Exception e){

                                throw (XMLLoadException) e.fillInStackTrace(); 

                    }

    }/* loadFromXML(String) */

    public int getNumDefinitions(){ return definitions.size(); }

    public void addDefinition(String def){ definitions.add(def); }

   

    public String getDefinitionAt(int index){

                try{

                    return  (String) definitions.get(index);

                }catch(IndexOutOfBoundsException ioobe){

                    return null;

                }

    }/* getDefinitionAt(int) */

    public String eraseDefinitionAt(int index){

                try{

                     return (String) definitions.remove(index);

                }catch(IndexOutOfBoundsException ioobe){

                    return null;

                }

    }/* eraseDefinitionAt(int) */

    public String replaceDefinitionAt(int index, String newDef){

                try{

                    return (String) definitions.set(index, newDef);

                }catch(IndexOutOfBoundsException ioobe){

                    return null;

                }

    }/* replaceDefinitionAt(int, String) */

   

} // JargonFileEntry

The JargonFileEntry class extends the_PersistableRemoteObjectImplBase which implements the PersistableRemoteObject interface. The JargonFile entry class uses the event-based Simple API for XML parsing (SAX) API to populate its attributes from an XML string. The SAX API was chosen because it is more efficient for dealing with large XML structures, and hence large objects, because it doesn’t have to store the entire string in memory all at once. The JargonFileEntry classes implements the DocumentHandler interface and thus contains methods used in parsing an XML string that are and populating its attributes from the XML string. The RemoteObjectServer that retrieves and stores the JargonFileEntry uses JDBC and Sun's JDBC-ODBC bridge to interact with the database. Below is a snippet from the RemoteObjectServer.java file showing the loadObject() method.

import RemoteObjectStore.*;

import org.omg.CORBA.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

import java.sql.*;

import java.util.*;

import sun.jdbc.odbc.JdbcOdbcDriver;

/**

  * This class sits on a server and satisfies remote requests for

  * PersistableRemoteObjects.

  */

public class RemoteObjectServer extends _RemoteObjectFactoryImplBase {

   

    private static final String dsn = "jdbc:odbc:Object_Store";

    private static final String user = "object_manager";

    private static final String pswd = "remote";

/* NOTE!!! LOTS OF CODE IN THIS CLASS HAS BEEN OMITTED FOR CLARITY */

    

    /**

     * Calls parent class constructor and loads new instance of Sun's

     * JDBC-ODBC bridge for interaction with SQL Server database.

     * @see _RemoteObjectFactoryImplBase#_RemoteObjectFactoryImplBase()

     */

    public RemoteObjectServer(){

                super();

                try

                    {

                                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver").newInstance();

                                System.out.println("Remote Object Server Launched");

                    }

                catch(Exception e)

                    {

                      throw new RuntimeException("JDBC-ODBC Driver load failure");

                    }

    } /* Constructor() */

    /**

     * Obtains an object from the remote object store. If the object does not

     * exist in the DB, a RuntimeException will be thrown.

     * @param objectID the object ID of the object to retrieve from the remote

     * object store.

     * @param proh this is a holder class that contains an empty

     * PersistableRemoteObject that will be populated with the object

     * created.

     * @exception ObjectLockedException if the objectID is currently locked

     * by another user.

     * @exception ObjectNotFoundException if the objectID could not be found

     * in the database.

     */

    public void loadObject(String objectID,

                                                   PersistableRemoteObjectHolder proh) throws

                                                       ObjectLockedException, ObjectNotFoundException{

               

                try{

                    Class objectClass = proh.value.getClass();

                /* connect to database */

                Connection conn =  DriverManager.getConnection(dsn, user, pswd);

                               

                /* search for object in DB */

                Statement sidStmt = conn.createStatement();

                // StringBuffer preferable to concatenating Strings with '+'

                StringBuffer sqlStmt =

                   new StringBuffer("select * from object_table where object_id = '");

                sqlStmt.append(objectID); sqlStmt.append("'");

                ResultSet rset = sidStmt.executeQuery(sqlStmt.toString());

               

                boolean in_database = rset.next(); 

      

                if(!in_database){

                     throw new ObjectNotFoundException();

                }

                /* create object that will be returned */

                String xmlString   = rset.getString(2);

                String objectType = rset.getString(3);

                // but first check to see if the object is locked

                if(rset.getInt("locked")==1)

                    throw new ObjectLockedException();

                if(objectClass.getName().equals(objectType.trim()) == false){

                    System.out.println("Class object doesn't match that in store");

                    throw new RuntimeException("Class object doesn't match that in store");

                }              

                PersistableRemoteObject pro = proh.value; 

                pro.objectID(objectID);

                pro.loadFromXML(xmlString);

                pro.creationID("" + System.currentTimeMillis());

                /* close connections to DB */

                sidStmt.close();

                conn.close();

               

                //              proh.value = pro;

               

                }catch(SQLException sqle){

                    sqle.printStackTrace();

                    throw new RuntimeException(sqle + "");

                }catch(RuntimeException re){

                    System.out.println(re.getMessage());

                    throw re;

                }catch(ObjectLockedException ole){

                    ole.printStackTrace();

                    throw ole;

                }catch(ObjectNotFoundException onfe){

                    onfe.printStackTrace();

                    throw onfe;

                }catch(Exception e){

                    e.printStackTrace();

                    throw new RuntimeException("Occured while in loadFromXML()" + e);

                }

    }/* loadObject(String) */

}

Upon creation RemoteObjectServer   the remote object server makes a call to its super class’s (RemoteObjectFactoryImplBase) constructor which in turn makes a call to its super class's constructor (org.omg.CORBA.DynamicImplementation). Also an attempt is made to load the JDBC-ODBC driver and if it fails the server will not start because a connection to the database cannot be made.   The loadObject() method uses a PersistableRemoteObjectHolder as a parameter because Java does not support inout or out parameters due to the fact that parameters as passed by constant value. A check is made to see if the classname of the object held in the PersistableRemoteObjectHolder matches that of the object stored in the database with the requested object ID before returning it to the client. It is possible to have implemented this function by using CORBA Introspection and instantiating the object via metadata passed instead of populating an object that was passed to the method.

The JargonFileClient locates the RemoteObjectServer via the CORBA COS (Common Object Services) Naming Service, which provides a tree-like directory for object references in the same way that a filesystem provides a directory structure for files. There is a Naming Service provided with Java IDL that ships standard with the Java 1.3 SDK which is a simple implementation of the COS Naming Service specification.

Object references are stored in the namespace by name and each object reference-name pair is called a name binding. Name bindings may be organized under naming contexts. Naming contexts are themselves name bindings and serve the same organizational function as a file system subdirectory. All bindings are stored under the initial naming context. The initial naming context is the only persistent binding in the namespace; the rest of the namespace is lost if the Java IDL name server process halts and restarts.

For the JargonFileClient to use COS naming, its ORB must know the name and port of a host running a naming service or have access to a stringified initial naming context for that name server. The naming service can be either the Java IDL name server or another COS-compliant name service. A code snippet showing how the JargonFileClient locates the RemoteObjectServer and also how the it uses the RemoteObjectServer’s loadObject() method described above follows:

import RemoteObjectStore.*;

import org.omg.CORBA.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

/**

  * This class provides an means to interact with a jargon file whose elements are stored

  * as objects in a remote object store over a network.

  */

public class JargonFileClient {  

   

    /**

     * We will retrieve objects from the remote object store via this object.

     */

    public static RemoteObjectFactory objFactory;

/* NOTE!!! LOTS OF CODE IN THIS CLASS HAS BEEN OMITTED FOR CLARITY */

    /**

     * This is the word currently being looked at by the user.

     */

    public static JargonFileEntry currentEntry;

/**

     * Retrieves a specified entry from the Jargon File

     */

    public static void locateEntry(){

                System.out.print(strSEARCH);

                String strUserInput = IOHelper.readLine();

               

                //we need to create an unused class so as to get a store

                //the JargonFileEntry class we will get back from the server  

                JargonFileEntry classProxy = new JargonFileEntry();

               

                PersistableRemoteObjectHolder proh =

                    new PersistableRemoteObjectHolder(classProxy);

                try{

                    objFactory.loadObject(strUserInput, proh);

                    /* unlock old JargonFileEntry */

                    if(currentEntry!= null){

                                objFactory.unlock(currentEntry);

                    }

                    currentEntry = (JargonFileEntry) proh.value;

                    /* lock new JargonFileEntry */

                    objFactory.lock(currentEntry);

                   

                    classProxy = null; /* no longer needed */

                    viewEntry();

                }catch(ObjectNotFoundException onfe){

                   

                    System.out.println(strDOESNT_EXIST);

                }catch(ObjectLockedException ole){

                   

                    System.out.println(strIS_LOCKED);

                }catch(RuntimeException re){

                   

                    System.out.println(re);

                    re.printStackTrace();

                }

    }/* locateEntry() */

/**

     * Interacts with user and retrieves word-definition pairs from remote

     * object store.

     * @param argv ignored.

     */

    public static void main(String[] args) {

               

        try {

                    /* Create and initialize the ORB */

                    ORB orb = ORB.init(args, null);

     

                    // Get the root naming context

                    org.omg.CORBA.Object objRef =

                                orb.resolve_initial_references("NameService");

                    NamingContext ncRef = NamingContextHelper.narrow(objRef);

     

                    /* Resolve the object reference in naming */

                    NameComponent nc = new NameComponent("RemoteObjectFactory", "");

                    NameComponent path[] = {nc};

                    objFactory =

                                RemoteObjectFactoryHelper.narrow(ncRef.resolve(path));

                    /* display user menu */

                    displayMenu();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }/* main(String[]) */

   

} // JargonFileClient

RMI Implementation

            The RMI interfaces for the ROPEM system are shown below.

package remote_obj_store;

import java.rmi.Remote;

import java.rmi.RemoteException;

/**

  * This Object stores and retrieves PersistableRemoteObject's to and from a

  * remote location.

  */

public interface RemoteObjectFactory extends Remote {

   

    PersistableRemoteObject createObject(String objectID, Class objectClass)

                throws RemoteException, ClassCastException;

    void storeObject(PersistableRemoteObject pro) throws RemoteException;

    PersistableRemoteObject loadObject(String objectID, Class objectClass)

                throws RemoteException;

    void lock(PersistableRemoteObject pro) throws RemoteException;

    void unlock(PersistableRemoteObject pro) throws RemoteException;

    boolean isLocked(String objectID) throws RemoteException;

} // RemoteObjectFactory

package remote_obj_store;

import java.io.Serializable;

import org.xml.sax.*;

import org.xml.sax.helpers.*;

/**

  * This is an interface for an Object that can generate XML representations

  * of itself and has an Object ID that can be used to uniquely identify it. 

  */

public interface PersistableRemoteObject extends Serializable {

   

    public String getCreationID();

   

    public void setCreationID(String  v);

   

    String getObjectID();

    void setObjectID(String  v);

  

   

    String getXMLRepresentation();

    void loadFromXML(String xmlString) throws Exception;

} // PersistableRemoteObject

            One noticeable difference between the interfaces specified in the RMI version of ROPEM and the CORBA version is that the CORBA version throws lots of specific Exceptions while the RMI version throws mainly RemoteException. The reason that the RMI version can get away with throwing RemoteException is that the RemoteException class supports storing error messages in it upon initialization while CORBA Exception classes do not.   The  JargonFileEntry class is very similar to that in the CORBA implementation except that it implements the PersistableRemoteObject interface directly as well as extends the HandlerBase class from the org.xml.sax package to avoid having to create functions that do nothing to fulfill interface requirements.

            On the other hand there is considerable difference between the implementations of methods like loadObject() and createObject() in the CORBA vs. RMI versions.

package remote_obj_server;

import java.rmi.*;

import java.rmi.server.*;

import remote_obj_store.*;

import java.sql.*;

import java.util.*;

import sun.jdbc.odbc.JdbcOdbcDriver;

/**

  * This class sits on a server and satisfies remote requests for

  * PersistableRemoteObjects.

  */

public class RemoteObjectServer extends UnicastRemoteObject

    implements RemoteObjectFactory {

   

    private static final String dsn = "jdbc:odbc:Object_Store";

  

    private static final String user = "object_manager";

    private static final String pswd = "remote";

    public RemoteObjectServer() throws RemoteException{

                  super(6661);

                try

                    {

                                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver").newInstance();

                                System.out.println("Remote Object Server Launched");

                    }

                catch(Exception e)

                    {

                      e.printStackTrace();

                      throw new RemoteException("Could not register JDBC-ODBC bridge");

                    }

    } /* Constructor() */

/* NOTE!!! LOTS OF CODE IN THIS CLASS HAS BEEN OMITTED FOR CLARITY */   

    /**

     * Obtains an object from the remote object store. If the object does not

     * exist in the DB, a RemoteException will be thrown.

     * @param objectID the object ID of the object to retrieve from the remote

     * object store.

     * @param objectClass the Class object for the object that shall be

     * retrieved.

     * @exception RemoteException see RemoteObjectFactory for details.

     */

   public PersistableRemoteObject loadObject(String objectID,Class objectClass)

       throws RemoteException{

               

                try{

                /* connect to database */

                Connection conn =  DriverManager.getConnection(dsn, user, pswd);

                               

                /* search for object in DB */

                Statement sidStmt = conn.createStatement();

                // StringBuffer preferable to concatenating Strings with '+'

                StringBuffer sqlStmt =

                   new StringBuffer("select * from object_table where object_id = '");

                sqlStmt.append(objectID); sqlStmt.append("'");

                ResultSet rset = sidStmt.executeQuery(sqlStmt.toString());

               

                boolean in_database = rset.next(); 

      

                if(!in_database){

                    StringBuffer errStr = new StringBuffer("ObjectID: ");

                    errStr.append(objectID);

                    errStr.append(" does not exist in remote object store");

                   

                    throw new RemoteException(errStr.toString());

                }

                /* create object that will be returned */

                String xmlString   = rset.getString(2);

                String objectType = rset.getString(3);

                // but first check to see if the object is locked

                if(rset.getInt(4)==1){

                    StringBuffer errStr = new StringBuffer("ObjectID: ");

                    errStr.append(objectID);

                    errStr.append(" is currently locked");

                    throw new RemoteException(errStr.toString());

                }

                //check to see if the Class object passed in is for the class in the

                //object store

                // NOTE: eliminate whitespace from string.

                if(objectClass.getName().equals(objectType.trim()) == false)

                  throw new RemoteException("Class object doesn't match that in store");

               

                PersistableRemoteObject pro =

                    (PersistableRemoteObject) objectClass.newInstance(); 

                pro.setObjectID(objectID);

                pro.loadFromXML(xmlString);

                pro.setCreationID("" + System.currentTimeMillis());

                /* close connections to DB */

                sidStmt.close();

                conn.close();

               

                return pro;

               

                }catch(SQLException sqle){

                    System.out.println(sqle);

                    throw new RemoteException(sqle + "");

                }catch(InstantiationException ie){

                    System.out.println(ie);;

                    throw new RemoteException(ie + "");

                }catch(ClassNotFoundException cnfe){

                    System.out.println(cnfe);

                    throw new RemoteException(cnfe + "");

                }catch(IllegalAccessException iae){

                    System.out.println(iae);

                    throw new RemoteException(iae + "");

                }catch(RemoteException re){

                    throw re;

                }catch(Exception e){

                    System.out.println(e);

                    throw new RemoteException("Occured while in loadFromXML()" + e);

                }

    }/* loadObject(String) */

} // RemoteObjectServer

A marked differences exist in the RMI implementation of loadObject() method from the CORBA implementation such as the fact that a Class object is passed to the method instead of a PersistableRemoteObjectHolder or similar means of getting an out parameter. Instead of using an out paramater the java.lang.Class object containing the metadata for that particular PersistableRemoteObject is marshaled and sent to the server (which is possible because it implements java.io.Serializable) . The Class object is then used to instantiate the PersistableRemoteObject which is returned to the client after being populated with the XML string from the database. Due to the fact that RMI supports dynamically loading classes across Java Virtual Machines, the class whose Class object is sent across the network doesn’t have to exist in the server environment to be instantiated but can be created entirely from the metadata contained in its Class object.

           

DCOM Implementation

The DCOM implementation had two major differences with the RMI and CORBA versions.  Firstly, we chose C++ rather than Java since it allows more fine-grained control of COM internals and direct access to IUnknown.  Secondly, XML parsing was handled by Microsoft's XMLDOM parser.  Since this parser uses the Document Object Model (DOM) API, it creates a complete representation of the XML document object in memory leading to increased memory overhead.  Initial parsing costs are also greater, but subsequent access should be much faster than with SAX.

           

// Dictionary.idl : IDL source for Dictionary.dll

//

// This file will be processed by the MIDL tool to

// produce the type library (Dictionary.tlb) and marshalling code.

import "oaidl.idl";

import "ocidl.idl";

                [

                                object,

                                uuid(EC12584D-93C5-4359-A152-53C70F5391FE),

                                dual,

                                helpstring("IWord Interface"),

                                pointer_default(unique)

                ]

                interface IWord : IDispatch

                {

                                [id(1), helpstring("method AddDefinitoin")] HRESULT AddDefinitoin(BSTR bstrDefinition);

                                [id(2), helpstring("method GetNumDefinitions")] HRESULT GetNumDefinitions([out,retval] long * pDefinitions);

                                [id(3), helpstring("method GetDefinitionAt")] HRESULT GetDefinitionAt(long index, [out,retval] BSTR * bstrDef);

                                [id(4), helpstring("method EraseDefinitionAt")] HRESULT EraseDefinitionAt(long index);

                                [id(5), helpstring("method ReplaceDefinitionAt")] HRESULT ReplaceDefinitionAt(long index, BSTR bstrDef);

                };

                [

                                object,

                                uuid(EC12584D-93C5-4359-A152-53C70F5391FA),

                                dual,

                                helpstring("IPersistableRemoteObject Interface"),

                                pointer_default(unique)

                ]

                interface IPersistableRemoteObject : IUnknown

                {

               

                                [propget, id(2), helpstring("property ObjectID")] HRESULT ObjectID([out, retval] BSTR *pVal);

                                [propput, id(2), helpstring("property ObjectID")] HRESULT ObjectID([in] BSTR newVal);

                                [propget, id(3), helpstring("property CreationID")] HRESULT CreationID([out, retval] BSTR *pVal);

                                [propput, id(3), helpstring("property CreationID")] HRESULT CreationID([in] BSTR newVal);

                                [id(4), helpstring("method GetXMLRepresentation")] HRESULT GetXMLRepresentation(BSTR* bstrXML);

                                [id(5), helpstring("method LoadFromXML")] HRESULT LoadFromXML(BSTR bstrXML);

                };

                [

                                object,

                                uuid(FE9A7B15-587D-4C6F-B6D0-070E6B7F769F),

                                dual,

                                helpstring("IRemoteObjectServer Interface"),

                                pointer_default(unique)

                ]

                interface IRemoteObjectServer : IDispatch

                {

                                [id(1), helpstring("method CreateObject")] HRESULT CreateObject(BSTR bstrObjectID, BSTR bstrType, [out,retval] IPersistableRemoteObject ** pRetVal);

                                [id(3), helpstring("method UnlockObject")] HRESULT UnlockObject(IPersistableRemoteObject * pObject);

                                [id(4), helpstring("method StoreObject")] HRESULT StoreObject(IPersistableRemoteObject* pObject);

                                [id(5), helpstring("method LoadObject")] HRESULT LoadObject(IPersistableRemoteObject* pObject);

                                [id(6), helpstring("method IsLocked")] HRESULT IsLocked(IPersistableRemoteObject* pObject, [out,retval] BOOL* pbLocked);

                                [id(7), helpstring("method LockObject")] HRESULT LockObject(IPersistableRemoteObject *pObject);

                };

                                [id(4), helpstring("method LockObject")] HRESULT LockObject(IPersistableRemoteObject* pObject);

[

                uuid(FCF8BF7C-C396-4747-AF49-EFB69C34A8CA),

                version(1.0),

                helpstring("Dictionary 1.0 Type Library")

]

library DICTIONARYLib

{

                importlib("stdole32.tlb");

                importlib("stdole2.tlb");

                [

                                uuid(A8DBEA02-129C-4BC0-BF62-0E686221DE88),

                                helpstring("Word Class")

                ]

                coclass Word

                {

                                [default] interface IWord;

                };

                [

                                uuid(556A2C3D-FCAD-4743-9D2D-C31FA49FF55E),

                                helpstring("RemoteObjectServer Class")

                ]

                coclass RemoteObjectServer

                {

                                [default] interface IRemoteObjectServer;

                };

};

The DCOM version of the Dictionary component uses multiple interfaces to support both client requirements and the distributed nature of the object. DCOM uses a technology known as Type Libraries to store metadata about the component and it's methods.  The type library maps perfectly to the component's definition which is specified in IDL.  The type library also provides the operating system with identification data so that clients can instantiate the component knowing only it's interface and coclass name.  A coclass defines the class that maps to a specific identifier.  Here, the CWord class maps to the Word identifier.  The Word component actually supports two different custom interfaces.  It supports IWord and IPersistableRemoteObject.  Clients that work with the word component fall into two categories.  C++ clients, which have full access to both the component and COM, simply request the particular interface which they want through QueryInterface.  Non-C++ clients usually only see the interface marked as [default].  Some languages, like Microsoft's version of java, are capable to accessing other interfaces by casting.  One of the many advantages of DCOM is the support for multiple languages.  This component is accessible through Java, JavaScript, Visual Basic as well as any other COM compliant language in addition to C++.  This is possible through marshalling, type libraries and automation.  Marshalling consists of converting a method invocation into a binary string.

Marshalling help support both the distributed nature of the component as well as intra-language functionality.  This works since COM can marshal the method call to and from the Microsoft Java Virtual Machine.

The DCOM version of the persistent object framework and the word component required a deep understanding of the fundamentals of DCOM as well as the specifics of various technologies required to actually develop working components.  One of the key aspects of our frameworks consists of persisting objects into a central repository as XML text. In order to use the XML parser and its support for verification, our components needed to act as a DCOM client for the XMLDOM component.  Since we did not have the headers, libraries or source code for the XMLDOM component, we used ATL as well as the compiler’s built in COM import feature.  ATL stands for ActiveX Template Library and it consists of several C++ template classes that make working with type libraries possible in C++ without the original headers or source. 

STDMETHODIMP CRemoteObjectServer::LoadObject(IPersistableRemoteObject *pObject)

{

                if(!pObject) return E_POINTER;

                // Hack.  Use the xml parser to load from a url.

                try

    {

                                _ConnectionPtr pConn("ADODB.Connection");

                                _RecordsetPtr pRs("ADODB.Recordset");

                                _CommandPtr pCmd("ADODB.Command");

                               

                                // Open a connection

                                pConn->Open("dsn=PersistableRemoteObject;", "", "", adConnectUnspecified);

               

                                // Create query

                                BSTR bstrObjectID = NULL;

                                pObject->get_ObjectID(&bstrObjectID);

                                CComBSTR bstrSQL = "select * from Object where object_id = '";

                                bstrSQL += bstrObjectID;

                                bstrSQL += "';";

                                SysFreeString(bstrObjectID);

                               

                               

                               

                                // Create a command

                    pCmd->CommandText = bstrSQL.m_str; // add where clause for object_id

                                pCmd->ActiveConnection = pConn;

                                // Execute the command

                                pRs->CursorLocation = adUseClient;

                                pRs->Open((IDispatch *) pCmd, vtMissing,

        adOpenStatic, adLockBatchOptimistic, adCmdUnspecified);

                                long iRecords = 0;

                                pRs->get_RecordCount(&iRecords);

                                if(iRecords != 1) return E_UNEXPECTED; //No Object

                                Fields* pFields = NULL;

                                pRs->get_Fields(&pFields);

                                Field* pXMLField = NULL;

                                _variant_t varXML = CComBSTR("xml");

                               

                                pFields->get_Item(varXML, &pXMLField);

                                _variant_t var;

                                pXMLField->get_Value(&var);

                                if(var.vt != VT_BSTR) return E_FAIL;

                                pObject->LoadFromXML(var.bstrVal);

                }

                catch(...)

                {

                                return E_FAIL;

                }

                return S_OK;

}

Here, _ ConnectionPtr is actually a smart pointer that overrides the C++'s indirection operator.  Whenever a method like pRS->Open () is called, the overridden indirection or arrow operator makes the proper call to the component based on the type library.  The compiler generates the class definition for the smart pointer at compile time.  This definition consists of the exact interface that is found in the type library.  Therefore, dynamically generated ATL smart pointers makes interacting with other DCOM components seamless to the C++ code.  Without ATL and dynamic COM import support, this would only be possible with source headers.  In addition to ADO database components, our framework also uses ATL to access the XMLDOM component.

It should be noted that DCOM’s primary means of handling remote errors is via the HRESULT which is returned by every remote function. The HRESULT is merely a numeric assignation which has various pre-defined values (S_OK for success, E_FAIL for error, etc). Due to the fact that all DCOM functions must return an HRESULT, all DCOM functions that want to return a value to the client must use out parameters. For richer error handling functionality Error Objects that implement IErrorInfo can be used to return more contextual error data to the client. If the server plans to support Error objects it must implement the ISupportErrorInfo interface.

Performance and Problems with Benchmarking

Our framework fully supports multi-tier distributed applications.  Although in theory an application could live on multiple servers scattered around the world and take advantage of our framework for persistence, it will not scale well.  An analysis of bottlenecks revealed that most of the problems were in the database connection and locking.  This is unfortunate because it meant that all the benchmark numbers we obtained to compare the performance of the same system written in CORBA, DCOM, and RMI were meaningless. Actually accessing the database was generally much faster than xml parsing or any other operation.  Secondly, our locking mechanism worked through polling.  If an object were locked, the code would keep trying again until the object was available.  The solution consists of using a mutex or messaging framework.  Messaging frameworks such as Microsoft Message Queue (MSMQ) or Java Message Services (JMS) provide loosely coupled transactional events that get queued by the component system until the proper resource is available.  On a lower level, an operating system mutex could be used to coordinate multiple threads awaiting the same resource.  Mutexes, however would not truly work in a distributed environment and would only make a single process more efficient.  Since the database was the major bottleneck, caching proved to be the most effective way to improve performance.  In a single process environment, moving a percentage of the objects to an in-memory data structure and then synchronizing the data structure with the database using a write-back scheme showed an order of magnitude improvement dependent on how often the same object was requested by clients.  Unfortunately, the strategy used would not scale to larger systems since a distributed cluster does not share memory.  With an event based synchronization, however, parts of the database could be transferred to whatever server needs the data most.

Transactional Systems

At the low level, component systems provide a component with language independence as well as distributed capabilities like inter-process method invocation.  Component systems can also provide functionality at much higher levels of abstraction.  A classic example is transactions.  Most enterprise systems rely on transactions for nearly all their functionality.  A transaction is a distributed atomic computational event.  This means that the computation is relevant to multiple systems at the same time and the transaction is either completed or not.  There is no such thing as a partial transaction.  Transactional operations usually consist of a set of smaller operations.  For instance, a commerce transaction consists of removing money from one account and adding it to another.  If only one of these operations succeeds, the transaction violates the system’s conceptual integrity.  It does not make sense for money to be removed from one account without being added to another.  Therefore, all transactional systems require rollback or an ability to undo operations until all interested parties have confirmed that they are satisfied with the state of the transaction.  Transactional component systems provide a set of distributed components with a transaction context.  This context stores relevant information about the transaction as well as success state.  The context may also know what components are interested in the transaction. 

One strategy for using transactional contexts is multi-phase commits.  The components that work on a transaction are part of a pipeline.  At any stage in the pipeline, the component may chose to abort the transaction.  If an abortion occurs, all previous components are told the operation failed and simply ignore any operations they have queued.  Once all pipeline stages complete, all components are told to get ready to commit.  Once they are all ready, the transactional system tells them to commit.  At this point, the chance of failure is very low.  Each component then sends the transactional system a confirmation.  If all confirmations are received, the transactional system proceeds to the next operation.  If at least one confirmation is not received, the transactional system tells the components to rollback or undo the work.  Therefore, either all interested components complete the transaction or return to their previous state.  In such a transactional system, there is no chance of the system integrity being compromised.  The transaction either goes through perfectly on all parts of the system or it fails completely.  Another advantage of transactional contexts is that they hold state. 

On web services with hundreds of servers spread across the globe serving millions of users, it would be very difficult to synchronize the data in a central place.  The solution consists of storing the transactional context in the web browser’s cookie.  Whatever server the web user’s action connects to can then use the cookie to recreate all relevant state.  Two technologies that add transactional support to distributed component system are Microsoft Transaction Server and Sun’s Enterprise Java Beans Transactions.  Transactional components must work with events that adhere to ACID properties.

Atomicity

A transaction can not complete in part.  It must either succeed or fail.

Consistency

If a transaction succeeds, all parts of the system agree on what happened.

Isolations

The order in which two transactions occur should not affect the outcome.

Durability

The transactional state is maintained in a form of media that can survive power or network outages.

Microsoft Transaction Server

MTS allows COM components to be part of distributed transaction.  The key feature MTS provides is context objects.  A context object handles database rollback as well as context.  When a transactional component is instantiated the Distributed Transaction Coordinator(DTC) provides it with a context object.  In C++ a component may get this object by calling GetObjectContext().  The DTC links this context object with a transaction provided.  One example of a transaction provider is SQL server.  When a transactional component accesses the database, the updates are queued instead of written.  The component thinks the database has been modified and tell MTS that it has completed successfully through the SetComplete() method on the context object.  If all phases of the transactional pipeline complete, the queued changes are committed to the database.  Otherwise they are ignored and the transaction is aborted.  Note that each transactional component does not need to deal with the details of rolling back their changes to the database.

X/Open Distributed Transaction Processing Model

Transactions in CORBA as well as the Java Transaction Architecture are modeled after the X/Open Distributed Transaction Processing Model.  This model lays the theoretical groundwork for transactions as well as defining specific interfaces that a transactional framework should support.  One aspect of this model is the transactional requirements attached to a every component.  X/Open describes several transactional modes that indicate how a component may accept or work with a transaction.

TRANSACTION_NONE

Transactions are not supported

TRANSACTION_READ_COMMITTED

Dirty reads are prevented; non-repeatable reads and phantom reads can occur

TRANSACTION_READ_UNCOMMITTED

Dirty reads, non-repeatable reads and phantom reads can occur

TRANSACTION_REPEATABLE_READ

Dirty reads and non-repeatable reads are prevented; phantom reads can occur

TRANSACTION_SERIALIZABLE

Dirty reads, non-repeatable reads and phantom reads are prevented

TX_REQUIRED

Must have a transactional context

TX_SUPPORTS

May use a transactional context if it exists

TX_REQUIRES_NEW

Requires new transactional context

TX_MANDATORY

Must have a unfinished transactional context

Giving all components a set of transactional requirements sets up constraints to prevents certain types of invalid subtransactions.  For instance, the component that subtracts money from a bank account should always get a new context.  If it gets an older context, then some invalid operations have been performed.  This ensures that deductions happen before anything else.  It is better to temporarily destroy money than it is to create it since you can give someone a refund but can’t automatically ask for money back.  The X/Open method supports both one-phase and two-phase commits.  The Object Transaction Service handles commits.

Authors’ Conclusion

            The purpose of our investigations was to learn about Distributed Object systems and how they relate to building robust three-tier applications. In our explorations we discovered how transactional systems work, how to write programs using the three most popular distributed object technologies as well as the complexities involved in creating a robust error handling mechanism in a distributed environment. In this regard we feel that our independent study was successful and look forward to continuing the development of ROPEM in the future.

           

Appendix A: Source code for CORBA implementation of ROPEM

I. RemoteObjectStore.idl

module RemoteObjectStore{

exception XMLLoadException{};

interface PersistableRemoteObject{

//the time the object was created on the server, used to determine which

//of the object instances this one is

attribute string creationID;

//unique object identifier

attribute string objectID;

//Generates an XML representation of the Object

string getXMLRepresentation();

//Populates the data in the object from an XML string

void loadFromXML(in string xmlString) raises (XMLLoadException);

};

exception ObjectAlreadyExistsException{};

exception ObjectNotFoundException{};

exception ObjectLockedException{};

interface RemoteObjectFactory{

//Checks to see if the object ID is not in use and if it isn't will

//populate the object passed in with the data for the class

void createObject(in string objectID, inout PersistableRemoteObject pro)

  raises (ObjectAlreadyExistsException);

//checks to see if the object is locked

boolean isLocked(in string objectID) raises (ObjectNotFoundException);

//Obtains an object from the remote object store.

  void loadObject(in string objectID, inout PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);

//This stores the specified PersistableRemoteObject in the remote object store

  void storeObject(in PersistableRemoteObject pro)

    raises (ObjectLockedException);

//Prevents any updates to the object until the object is unlocked

  void lock(in PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);

//  Releases a lock placed on an object, if unlocked by the original locker

  void unlock(in PersistableRemoteObject pro)

    raises (ObjectLockedException, ObjectNotFoundException);       

         

};

 

};

//idltojava -fno-cpp RemoteObjectStore.idl

II. RemoteObjectServer.java

import RemoteObjectStore.*;

import org.omg.CORBA.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

import java.sql.*;

import java.util.*;

import sun.jdbc.odbc.JdbcOdbcDriver;

/**

  * RemoteObjectServer.java

  *

  *

  * Created: Fri Nov 17 09:11:05 2000

  *

  * @author <a href="mailto:kpako@hotmail.com">Dare Obasanjo</a>

  * @version 1.0

  */

/**

  * This class sits on a server and satisfies remote requests for

  * PersistableRemoteObjects.

  */

public class RemoteObjectServer extends _RemoteObjectFactoryImplBase {

   

    /*=================================================================*/

    /*                I N S T A N C E    V A R I A B L E S             */

    /*=================================================================*/

      

 

    /*=================================================================*/

    /*                S T A T I C        V A R I A B L E S             */

    /*=================================================================*/

      

    /**

     * This is the name of the dsn that will be used when attempting to

     * connect to the database

     */

    private static final String dsn = "jdbc:odbc:Object_Store";

    /**

     * This is the name of the user name  that will be used when attempting to

     * connect to the database

     */

    private static final String user = "object_manager";

    /**

     * This is the name of the password  that will be used when attempting to

     * connect to the database

     */

    private static final String pswd = "remote";

    /*=================================================================*/

    /*                        C O N S T R U C T O R                     */

    /*=================================================================*/

    

    /**

     * Calls parent class constructor and loads new instance of Sun's

     * JDBC-ODBC bridge for interaction with SQL Server database.

     * @see _RemoteObjectFactoryImplBase#_RemoteObjectFactoryImplBase()

     */

    public RemoteObjectServer(){

                super();

                try

                    {

                                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver").newInstance();

                                System.out.println("Remote Object Server Launched");

                    }

                catch(Exception e)

                    {

                      throw new RuntimeException("JDBC-ODBC Driver load failure");

                    }

    } /* Constructor() */

   

    /*=================================================================*/

    /*                I N S T A N C E       M E T H O D S              */

    /*=================================================================*/

    /**

     * Creates a new PersistableRemoteObject and stores on the it in the

     * database.

     * @param objectID the objectID for the object that will be stored in the

     * database.

     * @param proh this is a holder class that contains an empty

     * PersistableRemoteObject that will be populated with the object

     * created.

     * @exception ObjectAlreadyExistsException if the objectID already exists

     * in the database.

     */

    public void createObject(String objectID,

                                                     PersistableRemoteObjectHolder proh) throws

                                                                  ObjectAlreadyExistsException{

               

                try{

               

                    Class objectClass = proh.value.getClass();

                    /* connect to database */

                    Connection conn =  DriverManager.getConnection(dsn, user, pswd);

               

                    //Turn off autocommit so that if a crash occurs in the middle of

                    //insertions there won't be data corruption.

                    conn.setAutoCommit(false);

                   

                /* search for object ID in DB */

                Statement sidStmt = conn.createStatement();

                // StringBuffer preferable to concatenating Strings with '+'

                StringBuffer sqlStmt =

                   new StringBuffer("select * from object_table where object_id = '");

                sqlStmt.append(objectID); sqlStmt.append("'");

                ResultSet rset = sidStmt.executeQuery(sqlStmt.toString());

               

                boolean in_database = rset.next(); 

                sidStmt.close();

                  

                if(in_database){

                    throw new ObjectAlreadyExistsException();

                }

                /* create new instance of class */

                PersistableRemoteObject pro = proh.value;

                   

                pro.objectID(objectID);

                pro.creationID("" + System.currentTimeMillis());

                /* store default representation in DB */

                PreparedStatement object_table_insrt = 

                  conn.prepareStatement("insert into object_table values(?,?,?,?,?)");

                object_table_insrt.setString(1, objectID);

                object_table_insrt.setString(2, pro.getXMLRepresentation());

                object_table_insrt.setString(3, objectClass.getName());

                object_table_insrt.setInt(4, 0);

                object_table_insrt.setString(5, pro.creationID());

                object_table_insrt.executeUpdate();

                /* commit the changes to the database */

                conn.commit();

                conn.setAutoCommit(true);

               

                /* close all connections to the database */

                object_table_insrt.close();

                conn.close();

               

                //proh.value =  pro;

                }catch(SQLException sqle){

                    sqle.printStackTrace();

                    throw new RuntimeException("" + sqle);

                }catch(ObjectAlreadyExistsException oaee){

                    oaee.printStackTrace();

                    throw oaee;

                }catch(Exception sqle){

                    sqle.printStackTrace();

                    throw new RuntimeException("" + sqle);

                }

    }/* createObject(String, Class) */

    /**

     * Obtains an object from the remote object store. If the object does not

     * exist in the DB, a RuntimeException will be thrown.