ORB/ODBMS Integration
in the Sunrise Project

Persistence of CORBA Objects

To solve the problems of storing C++ CORBA objects in an object database, we have experimented with three approaches to ODBMS-persistence of CORBA objects: The first approach makes CORBA object references persistent, but not transparently storable. The others provide transparent storability to CORBA object references, both in the case of references to local objects and in the case of references to remote objects.

The pseudopersistence and smart pointer-based persistence approaches are not restricted to "pure ODBMSs": through object-relational mapping, these approaches are applicable to relational DBMSs as well. Virtual persistence, however, applies only to the case of a virtual memory-based ODBMS (i.e., ObjectStore).

My PhD dissertation contains a performance evaluation of these three approaches. Pseudopersistence appears as the best performer, followed closely by smart pointer-based persistence.

Pseudopersistence

The Object Database Adapter uses the delegation (tie) approach to interface implementation. Only implementation objects are stored in the database; the corresponding tie objects are automatically instantiated by the ODA whenever they are needed and deleted when not needed. To allow the instantiation of tie objects, the ODA embeds a stringfied ODBMS reference to the corresponding implementation object into the id (ReferenceData) field of a CORBA reference to a persistent object. It also provides an object activation function that builds a tie object given an ODBMS reference to the corresponding implementation object.

All relationships between CORBA objects in a database are actually relationships between the corresponding implementation objects. An in-memory table of "active pseudopersistent objects" is kept by the ODA, allowing it to provide a function that returns the CORBA object reference associated with a given implementation object.

Smart Pointer-Based Persistence

Smart pointer-based persistence is similar to pseudopersistence, but in this case the ODA also makes CORBA references transparently storable. CORBA references are stored as instances of a smart pointer class that constructs the CORBA object (a tie to a persistent implementation object) when the smart pointer is dereferenced.

Virtual Persistence

Virtual persistence also makes CORBA references transparently storable. A CORBA reference is stored as a persistent pointer to an ORB-independent and minimum-sized representative of the CORBA object. The representative is swizzled into the CORBA object at page fault time; instantiation of tie objects happens at page fault time under this approach.

The Sunrise ODA

The unavailability of a CORBA-based environment with full support for object persistence motivated the development of the Sunrise ODA. This Object Database Adapter was initially targeted at Iona's ORB, Orbix, and Object Design's ODBMS, ObjectStore. We later ported the Sunrise ODA to a relational engine, mSQL, which was accessed in an object-oriented fashion (as an ODBMS), through a simple smart pointer-based object-relational mediator. Subsequently we ported it to a second ORB, Postmodern Computing's ORBeline. Currently there are releases of this ODA for: Our next step will be an ODA release for a full-fledged relational DBMS (initially Oracle), accessed through a commercially available object-relational mediator.

Rather than a replacement of the BOA, the Sunrise ODA is an add-on to the BOA, implemented as a library that uses and extends the BOA services (see figure below).

The "standard version" of the Sunrise ODA is portable to whatever environments for which the supported ORBs and ODBMSs are available, and is currently being used on Solaris 2.x and IRIX 5.x. The portability of this ODA version across ODBMSs is pretty high. Its portability across ORBs is not as high, due to holes in the BOA specification.

The ODA keeps no ORB-specific information in the database; a change to another ORB requires no schema evolution. It is even possible for a a single database to be simultaneously accessed by CORBA servers based upon different ORBs.

Additional Information:

Persistence Approaches Supported

The standard version of the Sunrise ODA provides pseudopersistent C++ CORBA objects. An extended version supports also virtual persistence, and lets the interface implementor freely mix these approaches within a server. We wrote the extended version with the goal of allowing a particular CORBA server to achieve the tradeoff between persistence and performance levels that best suits the objects it implements. So far this extended version integrates Orbix and ObjectStore, runs on Solaris 2.x, and requires the use of a particular C++ compiler, the SPARCompiler 4.0.1 (Note 1).

Even in its standard version, the Sunrise ODA also supports mere persistence of implementation objects. This is a naive approach that makes implementation objects (not full CORBA objects) persistent, but does not make CORBA object references persistent. Hence, persistence of implementation objects is not an actual solution to the problem of object persistence. This option is present mostly for historical reasons; it was the first approach we implemented. It is still has some utility today, for top-level objects. Among the many objects implemented by a CORBA server, there is usually a "top-level" one, which represents the server itself. The top-level object of a server is known to its clients, which use it to start interacting with the server. A top-level object uses simple persistence of implementation and remains active as long as its server is active, because under this approach it can be assigned an id (marker, in Orbix terminology) arbitrarily chosen by its server (Note 2). With this scheme, a server can choose an id known to its clients, which in turn use this id to locate the server's top-level object (Note 3).

Support for Local Transactions

In an ODBMS, every access to a persistent object must be performed within a transaction. The ODA enforces this rule by automatically starting a transaction before each IDL-defined operation on a persistent object begins execution (if there is no transaction already active) and commiting (or aborting) the auto-started transaction at the end of the operation. In this default mode, each IDL operation corresponds to a database transaction.

Sometimes multiple IDL operations must be performed within a single transaction. For these cases, the ODA API provides functions that allow a sequence of operations to be grouped in a single transaction.

Dependency Upon ORB and ODBMS Features

Among the Orbix and ORBeline features not generally available in all ORBs, the following ones are used by the ODA: The ODBMS features used by the ODA depend on its version (standard or extended), that is, on the approaches to persistence of CORBA objects it supports.

Pseudopersistence uses only object identity (ODBMS object references), plus the ability to convert ODBMS references to strings and vice-versa. Because these are fairly common ODBMS features, other ODBMSs can be employed instead of ObjectStore. All that is needed is a simple adaptation of the ODA code. With the use of an object-relational mediator, even a relational DBMS can be employed. In this case, primary keys play the role of ODBMS references. The mSQL release of ODA demonstrates the applicability of the pseudopersistence scheme to relational DBMSs.

Virtual persistence, however, requires also a virtual memory-based ODBMS that offers page fault hooks (i.e., ObjectStore's os_access_hooks). In the case of a smart pointer-based ODBMS, smart pointer-based persistence should be implemented instead.

ODA Utilization by TeleMed

The Sunrise ODA is currently providing persistence of CORBA objects to TeleMed, a distributed, object-oriented, CORBA-based patient record system. TeleMed objects are stored in ObjectStore databases and accessed via Orbix, through IDL-defined interfaces. Except for top-level objects, for which persistence of implementation is employed, CORBA objects use the pseudopersistence approach. Due to both performance and DBMS independence concerns, we decided not to use virtual persistence at the present time.

The relationships between CORBA objects in a TeleMed database are therefore represented by links between implementation objects; ObjectStore allows the use of plain C++ pointers to implement these links. The relationships between CORBA objects in different TeleMed databases are represented by CORBA references converted to strings.

This scheme provides maximum efficiency and a good level of DBMS independency, at the cost of some increase in the complexity of the server code. As an example of this increase in code complexity, consider a situation in which it would be desirable to have a homogeneous list of CORBA references, whose elements may refer either to local or to remote objects. Instead of a single and homogeneous list, two separate lists must be introduced: one with pointers to local implementation objects, other with stringfied CORBA references to remote objects.

Notes

  1. The extended ODA is not portable: besides being targeted at a virtual memory-based ODBMS, virtual persistence uses compiler dependent information on the memory layout of C++ objects.

  2. ODA-assigned object reference ids are used in the pseudopersistence and virtual persistence approaches.

  3. Locating servers by their ids is an interim solution. Such practice, supported by Orbix in substitution to an actual name service, is not in the spirit of the OMG architecture: according to CORBA, object ids should only be meaningful to the object implementations themselves. This use of ids will be dropped when Iona's implementation of the CORBA Name Service becomes available.

  4. Although object activation is in the CORBA standard, we have included it in this list because it is neither fully defined by CORBA nor supported by all ORB implementations.

Using the Sunrise ODA: an Example

The example below uses the ODA release for ORBeline and ObjectStore. Another example, this one including a CORBA client written in Java, can be found here.

IDL Interfaces (File plib.idl)

This file defines the IDL interfaces book and library. C++ interface classes with the same names are generated by the IDL compiler.
interface book {
    readonly attribute string author;	
    readonly attribute string title;
};

typedef sequence<book> bookList;

interface library {
    readonly attribute string name;
    void add_book(in string name, in string author);
    bookList book_list();
};

Header File with the Implementation Classes (File plib.h)

Book and Library are the implementation classes for book and library, respectively. So a book is a CORBA object, and has a Book as its implementation object.
#ifndef PLIB_H
#define PLIB_H

#ifndef _SCHEMA_     // see schema.cc
#include <plib_s.hh> // IDL-generated skeletons
#endif

#include <oda.h>     // ODA header file

// auxiliary string class

class String {
public:    
    String(const char* value = "");
    virtual ~String() { delete[] _value; }
    static os_typespec* get_os_typespec(); // for ObjectStore
    const char* c_str() { return _value; }
    char* value() const { return strcpy(new char[strlen(_value)+1], _value); }
private:
    char* _value;
};

// implementation class for book

class Book {
public:
    Book(const char* author, const char* title);
    virtual ~Book() {}
    static os_typespec* get_os_typespec(); // for ObjectStore
    char* author() const { return _author.value(); }
    char* title() const { return _title.value(); }
private:
    String _author;
    String _title;
};

// implementation class for library

class Library {
public:
    Library(const char* name = "");
    virtual ~Library() {}
    static os_typespec* get_os_typespec(); // for ObjectStore
    char* name() const { return _name.value(); }
    void add_book(const char* name, const char* author);
#ifndef _SCHEMA_ // because bookList is an IDL-generated class
    bookList* book_list() const;
#endif    
private:
    String _name;
    os_List<Book*>& _book_list; // n.b.: Book, not book
}; 

#ifndef _SCHEMA_

// ODA directives

ODA_def_server(library)  // the top-level object in the server is a library
ODA_def_persistent(book) // books are made persistent by the ODA

// dummy class, just to force template instantiation with SunPro C++ 4.0
// (_oda_persistent_book is a template class whose definition
//  is generated by the directive ODA_def_persistent(book), above.)

static void dummy() {
    _oda_persistent_book<Book>::force_template_instantiation();
}

#endif

#endif

Since we are using the pseudopersistence approach, persistent relationships between CORBA objects are expressed in terms of the underlying implementation objects. So the library keeps a list of Books, not a list of books.

Member Function Definitions for the Implementation Classes (File plib.cc)

#include "plib.h"

extern CORBA::ORB_var orb;

String::String(const char* value)
    : _value(new(os_segment::of(this),
		 os_typespec::get_char(), 
		 strlen(value) + 1) char[strlen(value) + 1]) {
    strcpy(_value, value);	
}

Book::Book(const char* author, const char* title)
    : _author(author), _title(title) {}

Library::Library(const char* name)
    : _name(name),
      _book_list(os_List<Book*>::create(os_segment::of(this))) {}

void Library::add_book(const char* name, const char* author) {
    _book_list.insert(new(os_segment::of(this),
		      Book::get_os_typespec()) Book(name, author));
}

bookList* Library::book_list() const {
    CORBA::ULong len = _book_list.cardinality();
    book_ptr* buf = bookList::allocbuf(len);
    for (CORBA::ULong i = 0; i < len; i++) {
	// get a CORBA reference a persistent object of interface class book
	// (The argument to ODA_persistent_book 
	//  is an implementation object, of class Book,
        //  retrieved from the _book_list.
        //  The code for ODA_persistent_book is automatically generated
        //  by the directive ODA_def_persistent(book).)
	buf[i] = ODA_persistent_book(_book_list.retrieve(i)); // ********
	book::_duplicate(buf[i]);
    }
    return new bookList(len, len, buf, 1);
}

In the line marked with "********", we are calling the ODA to get a CORBA reference to a book object. If this book is not currently active, the ODA creates a tie to the implementation (Book) object passed to ODA_persistent_book, and embeds a stringfied ODBMS reference to this implementation object into the id field of the book object reference. Object references generated by the ODA are persistent, in the sense specified by CORBA.

Database Schema Definition (File schema.cc)

// This file is _not_ compiled with CC.
// Instead, it is processed by ossg (ObjectStore's schema generator),
// which creates the database schema.

#include <ostore/ostore.hh>
#include <ostore/manschem.hh>

#ifndef _SCHEMA_

// Unfortunately ossg does not yet accept some C++ constructs
// generally accepted by C++ compilers. Because such constructs
// appear in ORBeline header files and IDL-generated C++ files,
// these files cannot be exposed to ossg. We are currently
// circunventing this problem with the _SCHEMA_ definition
// below.

#define _SCHEMA_

#endif


// _SCHEMA_ will be #defined during schema generation only.
// (There is nothing magic with this name, any valid identifier
// could be used instead.)
// Application header files included here should use #ifdefs 
// to hide from ossg any ORBeline header files, IDL-generated source
// files, and interface types.

#include "plib.h" // contains "#ifdef _SCHEMA_" directives

// this is how we tell ossg about Library and Book objects

void dummy() {
    OS_MARK_SCHEMA_TYPE(Library);
    OS_MARK_SCHEMA_TYPE(Book);
}

CORBA Server Mainline (File server.cc)

Note that this CORBA server is an ObjectStore client; it gives CORBA clients access to objects persistently stored in an ObjectStore database.
#include "dbname.h"

// one and only one source file must #define ODA_STATIC_DEFS before
// including oda.h (since plib.h includes oda.h, we are doing it here)

#define ODA_STATIC_DEFS
#include "plib.h"

CORBA::ORB_var orb;
CORBA::BOA_var boa; 

int main(int argc, char * const *argv)
{
    // ObjectStore setup

    objectstore::initialize();
    os_collection::initialize();
    OS_ESTABLISH_FAULT_HANDLER;
    
    // initialize ORB
    
    orb = CORBA::ORB_init(argc, argv);
    boa = orb->BOA_init(argc, argv);
    
    // setup the databse file name and open it
    
    os_database* db = os_database::open(app_db_name, 0, 0664);
    
    // initialize ODA
    // (The second parameter below is the number of tie objects
    //  cached by the ODA. This parameter defaults to 1023.
    
    ODA::initialize("TestDB", 500);

    // The ODA ensures that every operation is performed within
    // a database transaction. The default mode is "transaction
    // per operation". The ODA starts and ends transactions
    // to encompass every operation by a transaction. Read-only
    // transactions are used by default. The call below tells the
    // ODA to use an update transaction in the case of the operation
    // add_book of the library interface.
	
    ODA::register_update_op("library", "add_book");
    
    // start a database transaction and look for the Library object
    
    os_transaction::begin(os_transaction::update);
    os_database_root* a_root = db->find_root("Library");
    if (!a_root) {   // then create it
	a_root = db->create_root("Library");
	a_root->set_value(new(db, Library::get_os_typespec()) Library(),
			  Library::get_os_typespec());
    }
    
    // get the Library object
    
    Library* lib_impl =
	(Library*) a_root->get_value(Library::get_os_typespec());
    
    // create a tie to this Library
    
    _oda_library_server<Library> lib(lib_impl, "Test");
    
    // end of transaction, changes are commited to the database
    
    os_transaction::commit();
    
    // server is ready to handle requests
    
    boa->obj_is_ready(&lib);
    boa->impl_is_ready("TestDB", ODA::activator());

    OS_END_FAULT_HANDLER;
    
    return 0;
}

Summary of Results from my PhD Research

As for which of these two approaches will be the most used, this will depend on the degree of interconnectivity between the heterogeneous components of the persistent object systems that we expect to become common. For CORBA objects implemented by a single server, pseudopersistence is sufficient. Smart pointer-based persistence provides additional convenience to express relationships between CORBA objects implemented by different servers.


The ORB/ODBMS Integration Page Francisco Reverbel's Home Page

[HTML 3.0 (Beta) Checked!]

--> Last modified:
Francisco Reverbel
reverbel at ime.usp.br