The IOPC library [04] contains a new API for O/R mapping. This new interface coexists with the old POLiTe-style interface inside one library. The interface is (with few exceptions) clearly divided into two parts - the IOPC and the POLiTe part. The POLiTe part provides backward compatibility with the original POLiTe library.
The IOPC library offers all features of the original POLiTe library. We will look only at the new IOPC interface. New features and main differences are listed below:
No need to describe the structure of persistent classes. Persistence works almost transparently.
All three basic types of class hierarchy mapping are supported - horizontal, vertical and filtered. Combinations of these types in one class hierarchy are also allowed (with a few exceptions).
Database views that join data from all mapping tabes are generated. These views are used for object loading and they also represent a simple read-only interface for non-library users.
Ability to define new persistent data types.
Loading persistent object attributes by groups. Persistent attributes can be divided into several groups which can be loaded separately.
Easy implementation of new RDBMS into the library[16].
The IOPC library consist of a number of modules, some of them are standalone applications. The hi-level architecture overview can be best explained on the library workflow displayed in Figure 3.8, “The IOPC library workflow”.
Three main modules can be observed from the figure:
IOPC SP uses OpenC++[17] parser and source-to-source translator to modify the source code to add support of the object persistence and generates XML metamodel description of structure of persistent structure.
IOPC DBSC generates SQL scripts from the XML metamodel description. The SQL scripts create required database structured needed for the IOPC object-relational mapping.
IOPC LIB is a library that provides the object persistence services to a program to which it is linked. It uses the metamodel generated by the IOPC SP module and database structures created using the IPOC DBSC scripts.
XML metamodel description loading and storing is performed by two statically-linked libraries / classes -
XMLMetadataLoader
andXMLMetadataWriter
. Both classes use the Xerces[18] parser to handle the XML files. Other type of metamodel storage can be implemented by inheriting from theMetadataLoader
andMetadataWriter
interfaces.
One of the most interesting aspects of the IOPC library is a new approach to metamodel description retrieval. User-created specification of the class structure is not needed. The source classes are parsed and metamodel description is collected by the IOPC SP module.
IOPC SP is a standalone executable created from patched OpenC++ source code and a OpenC++ metaclass IopcTranslator
. The metaclass affects the source-to-source translation of persistent classes done by the OpenC++ by performing the following operations:
Generates the set and get methods for all persistent attributes. Setters modify the dirty status of the objects and getters ensure that corresponding attribute groups are loaded.
Modifies every reference to persistent attributes so that they use the generated get and set methods.
Generates additional members to the processed classes needed by the IOPC LIB.
Inspects the processed classes and writes information about their structure to a XML file by calling the
MetadataWriter
(its implementationXMLMetadataWriter
).
In the end, IOPC SP runs compiler and linker on the translated source code.
As you can see in the Example 3.7, “Definition of persistent classes in IOPC”, persistent classes in IOPC does not need any additional descriptive macros for the persistence layer to be able to understand their structure. The only rules are that they need to be descendants of the IopcPersistentObject
and that the attribute types need to be supported by IOPC. For example, IOPC does not understand the std::string
STL type, only the basic C/C++ string representation char*
(or its wide character variant) is supported. If the string is allocated dynamically, it needs to be deallocated in the desctructor.
Example 3.7. Definition of persistent classes in IOPC
class Person : public IopcPersistentObject { public: char* name; short int age; Person() { name = NULL; } virtual ~Person() { if (name != NULL) free(name); } }; class Employee : public Person { public: int salary; }; class Student : public Person { public: char* studcardid; Ref<Employee> supervisor; Student() { studcardid = NULL; } virtual ~Student() { if (studcardid != NULL) free(studcardid); } };
Processed source code generated by the IOPC SP is deleted immediately after compilation. It is not meant to be modified by developers. Following example displays the processed Student
class. It was stripped by the age attribute because the listing was too long:
class Person : public IopcPersistentObject { public: Person() { set_name ( __null ) ; } virtual ~Person() { Free(); if ( m_name != __null ) free ( m_name ) ; } protected: char * m_name; bool m_name_isValid; public: virtual char * get_name() { if (!m_isPersistent || m_name_isValid || !m_classObject->isAttributePersistent(1)) return m_name; m_classObject->loadAttribute(1, this); if (!m_name_isValid) throw IopcExceptionUnexpected(); return m_name; } public: virtual char * set_name(char * _name) { m_name =_name; m_name_isValid = true; if (m_isPersistent) MarkAsDirty(); return _name; } protected: void iopcInitObject(bool loadingFromDB) { if (loadingFromDB) { m_name_isValid = false; } else { m_classObject = IopcClassObject::getClassObject(ClassName(), true); m_name_isValid = true; } } virtual int iopcExportAttributes(IopcImportExportStruct * data, int dataLen) { if (dataLen != 1) return 1; data[0].valid = m_age_isValid; if (m_age_isValid) data[0].shortVal = m_age; return 0; } virtual int iopcImportAttributes(IopcImportExportStruct * data, int dataLen) { if (dataLen != 1) return 1; if (data[0].valid) { if ((m_name_isValid) && (m_name)) free(m_name); m_name = strdup(data[1].stringVal); m_name_isValid = true; } return 0; } public: static const char * ClassName() {return "Person";} public: static IopcPersistentObject * iopcCreateInstance() { IopcPersistentObject * object = new Person; object->iopcInitObject(true); return object; } static RefBase * iopcCreateReference() { return new Ref<Person>; } }; static IopcClassRegistrar<Person> Person_IopcClassRegistrar("Person");
Rather larger amount of code was inserted into the class definition. IOPC SP generated setter and getter methods for the attributes, serialisation and deserialisation routines (iopcExportAttributes
and iopcImportAttributes
) and other methods needed by the persistence layer.
Not only the class code was translated. Code that reads or sets attribute values was changed to use the getter and setter methods (like we would use in the POLiTe library):
// original: Employee e; e.age = 45; // translated assignment operation: e.set_age(45);
IOPC SP also generates a XML metamodel file describing structure of the classes, see Example 3.8, “XML metamodel description file”.
Example 3.8. XML metamodel description file
<iopc_mapping project_name="example"> <class name="Employee"> <base_class>Person</base_class> <mapping db_table="EMPLOYEE" type="inherited"> <group name="default_fetch_group" persistent="true"> <attribute db_column="SALARY" db_type="NUMBER(10)" name="salary" type="int"/> </group> </mapping> </class> <class name="Person"> <mapping db_table="PERSON" type="vertical"> <group name="default_fetch_group" persistent="true"> <attribute db_column="AGE" db_type="NUMBER(10)" name="age" type="short"/> </group> <group name="1st_persistent_group" persistent="true"> <attribute db_column="NAME" db_type="VARCHAR2(4000)" name="name" type="char *"/> </group> </mapping> </class> <class name="Student"> <base_class>Person</base_class> <mapping db_table="STUDENT" type="inherited"> <group name="default_fetch_group" persistent="true"> <attribute db_column="SUPERVISOR" db_type="NUMBER(10)" name="supervisor" type="Ref<Employee>"/> </group> <group name="1st_persistent_group" persistent="true"> <attribute db_column="STUDCARDID" db_type="VARCHAR2(4000)" name="studcardid" type="char *"/> </group> </mapping> </class> </iopc_mapping>;
As mentioned before, persistent attributes can be divided into several groups which are handled by IOPC separately. By default, two groups are generated - a default_fetch_group
containing all numeric attributes and a 1st_persistent_group
containing attributes of all remaining data types (strings). The XML metamodel file can be customized by developers before running IOPC DBSC.
IOPC DBSC is a standalone executable that generates SQL scripts for various purposes - in particular the scripts to create or delete database structures required by the object-relational mapping. It uses MetadataLoader
to load the persistent class metamodel written by the IOPC SP. SQL script created from the previously generated XML metamodel file follows.
First it creates database table for the class list and fills it:
CREATE TABLE EXAMPLE_CIDS ( CLASS_NAME VARCHAR2(64) CONSTRAINT EXAMPLE_CIDS_PK PRIMARY KEY, CID NUMBER(10) NOT NULL CONSTRAINT EXAMPLE_CIDS_UN UNIQUE ); INSERT INTO EXAMPLE_CIDS VALUES ('Employee', 1); INSERT INTO EXAMPLE_CIDS VALUES ('Person', 2); INSERT INTO EXAMPLE_CIDS VALUES ('Student', 3);
Followed by a table (main table) containing OIDs of all persistent objects in the project:
CREATE TABLE EXAMPLE_MT ( OID NUMBER(10) CONSTRAINT EXAMPLE_MT_PK PRIMARY KEY, CID NUMBER(10) CONSTRAINT EXAMPLE_MT_FK REFERENCES EXAMPLE_CIDS(CID) ); CREATE INDEX EXAMPLE_MT_CID_INDEX ON EXAMPLE_MT(CID);
Then the mapping tables associated with persistent classes are created (we used the default vertical mapping):
CREATE TABLE PERSON ( OID NUMBER(10) CONSTRAINT PERSON_PK PRIMARY KEY CONSTRAINT PERSON_CD REFERENCES EXAMPLE_MT(OID) ON DELETE CASCADE, CID NUMBER(10) CONSTRAINT PERSON_FK2 REFERENCES EXAMPLE_CIDS(CID), AGE NUMBER(10), NAME VARCHAR2(4000) ); CREATE INDEX PERSON_CID_INDEX ON PERSON(CID); CREATE TABLE EMPLOYEE ( OID NUMBER(10) CONSTRAINT EMPLOYEE_PK PRIMARY KEY CONSTRAINT EMPLOYEE_CD REFERENCES EXAMPLE_MT(OID) ON DELETE CASCADE, SALARY NUMBER(10) ); CREATE TABLE STUDENT ( OID NUMBER(10) CONSTRAINT STUDENT_PK PRIMARY KEY CONSTRAINT STUDENT_CD REFERENCES EXAMPLE_MT(OID) ON DELETE CASCADE, STUDCARDID VARCHAR2(4000), SUPERVISOR NUMBER(10) );
Finally, two views are created for each persistent class. Simple views (SV
suffix) join all tables needed for loading complete instances of particular persistent classes. Their columns correspond to attributes of the associated classes. Polymorphic views (PV
suffix) have same structure as simple views: the difference is that they return not only instances of the associated classes, but also instances of their descendants.
CREATE VIEW Person_sv AS SELECT OID, AGE, NAME FROM PERSON WHERE CID = 2; CREATE VIEW Person_pv (CID, OID, AGE, NAME) AS SELECT CID, OID, AGE, NAME FROM PERSON WHERE PERSON.CID IN (2, 1, 3); CREATE VIEW Employee_sv (OID, AGE, NAME, SALARY) AS SELECT EMPLOYEE.OID, AGE, NAME, SALARY FROM PERSON, EMPLOYEE WHERE EMPLOYEE.OID = PERSON.OID; CREATE VIEW Employee_pv (CID, OID, AGE, NAME, SALARY) AS SELECT 1, EMPLOYEE.OID, PERSON.AGE, PERSON.NAME, SALARY FROM PERSON, EMPLOYEE WHERE EMPLOYEE.OID = PERSON.OID; CREATE VIEW Student_sv (OID, AGE, NAME, STUDCARDID, SUPERVISOR) AS SELECT STUDENT.OID, AGE, NAME, STUDCARDID, SUPERVISOR FROM PERSON, STUDENT WHERE STUDENT.OID = PERSON.OID; CREATE VIEW Student_pv (CID, OID, AGE, NAME, STUDCARDID, SUPERVISOR) AS SELECT 3, STUDENT.OID, PERSON.AGE, PERSON.NAME, STUDCARDID, SUPERVISOR FROM PERSON, STUDENT WHERE STUDENT.OID = PERSON.OID;
IOPC LIB is a shared library that represents the core of the IOPC project. It is linked to the outputs (object files) of IOPC SP and provides the run-time functionality.
IOPC LIB is built on the POLiTe library, it uses some of its components and exposes its new interface side-by-side with the original POLiTe interface. The result is that the IOPC library can be used almost the same way as its predecessor. It supports the POLiTe-style persistent objects as well as new persistent objects inherited from the IopcPersistentObject
class and processed with IOPC SP. Components from the POLiTe library that IOPC LIB uses are displayed in Figure 3.9, “POLiTe library components used in IOPC LIB”.
Because the object cache and the object references are reused from the POLiTe library, persistent object enter same states using same state transitions as in the section called “Persistent object manipulation”. There is, however, one problem with the current implementation in that it does not implement the locking correctly and all persistent objects look like unlocked all the time. So the Locked Local Copy state can be entered only by instances of the POLiTe persistent classes.
The query language and all related classes or templates are also reused, so there is no change in this area either.
IOPC persistent objets can be associated exclusively using references as described at the end of the the section called “Metamodel and object-relational mapping.”. Relations described earlier in that section can be used only for the POLiTe persistent objects. IOPC adds a new RefList<T>
template to the standard POLiTe Ref<T>
reference representing a persistable list of references. The list is stored into a separate table (one per project) which unfortunately does not have standard many-to-many join table schema. Each instance of the RefList<T>
is stored as a linked list of OIDs of its members. The table is completely unusable in SQL queries unless we are using its recursive features (if available) or procedural constructs like cursor iteration. Second issue is that these lists are always persistent. Every change is immediately propagated to the database regardless of the state of its owner, object cache is also bypassed. This renders the usage of RefList<T>
in combination with transient objects quite dangerous as orphaned entries are created in the "join" table. Same problem occurs if persistent object with RefList<T>
as a attribute is deleted - the associated linked list is not removed from the database.
Figure 3.10, “Structure of the IOPC LIB” displays the structure of the IOPC library and the relationship between new IOPC- and the original POLiTe interface.
Database layer uses slightly modified original interface. Along with the Database
, Connection
and Cursor
classes there is a new interface class - DatabaseSqlStatements
. It serves as an interface for database-dependent SQL statement generation. IOPC contains Oracle 8i implementation of these interface classes (using OCI 8).
IOPC persistent classes are created as descendants of the new base class - IopcPersistentObject
. The original base class Object
and its descendants (ImmutableObject
, DatabaseObject
and PersistentObject
) were preserved and can be used to create POLiTe persistent classes. Note that IopcPersistentObject
is derived from the original Object
class allowing it to be used with POLiTe components like object cache or references.
For each descendant of the IopcPersistentObject
class there is one IopcClassObjectImpl
instance. The purpose of this class is very similar to the concept of prototypes described in the the section called “Metamodel and object-relational mapping.” - it contains all data needed for object-relational mapping of the associated class. IopcClassObjectImpl
actually performs the mapping process. The class is linked to the POLiTe prototype system using the IopcProtoBaseAdaptor
which maps the prototype interface on the IopcClassObjectImpl
instance. Calls invoked on its ProtoBase
interface are delegated back to IopcClassObjectImpl
. Public interface to the IopcClassObjectImpl
class is provided by the IopcClassObject
class.
As the name suggests, IopcClassObjectContainer
is a place where class object instances are stored. Its first responsibility is to load list of classes from the MetadataLoader
and compare them with the class list stored in the database. Then it creates and initializes the IopcClassObjectImpl
instances. Similarly to ClassRegister
, it provides methods allowing us to find the class objects by name or by class id.
The main goal of the IOPC library was to make the usage of the POLiTe library simpler and more transparent. The architecture of the library was redesigned for the sake of these requirements. At first glance, the IOPC library looks like a big improvement over the original library, but if we look further into the source code and used technologies, we come upon several critical issues that may cause the usage and deployment of this library almost impossible.
IOPC uses the OpenC++ as a way to retrieve information about the structure of persistent classes. The OpenC++ project seems to be almost dead, last commit to the project CVS occurred in 2005. OpenC++ cannot handle most of template constructs, processing files that include GCC STL headers (tested on versions 3.4 and newer) produces a lot of errors. Because the source-to-source translation is used, users cannot be sure if any of their code translated with errors or warnings[19] will do what was intended. Probably for this reason the IOPC allows only the C strings (char* and w_char*), not the C++ STL strings (std::string and std::wstring).
Next, IOPC uses its own modified version of OpenC++ (called 2.6.t.0) and integrates it into the IOPC SP utility. This approach renders further maintenance of the OpenC++ code difficult. New changes from the OpenC++ CVS have to be merged manually into the IOPC SP source.
Second point is a question, why there are two parallel interfaces in the IOPC library - one for the new persistent objects and one for the POLiTe objects. If there is no known implementation that uses the POLiTe library, there is no need to be backward-compatible. The POLiTe part of the source code will just remain unmaintained (as no one is supposed to use the POLiTe objects in new applications). Because several IOPC objects inherit from the POLiTe classes, many inherited methods do not make sense any more. This makes the API less comprehensible and can lead to user's confusion. An example of this situation is the IopcProtoBaseAdaptor
which contains a number of methods commented as "Fake function". Second example is the mentioned local copy locking problem. Are we supposed to use the locking only on the POLiTe persistent objects or is it just a bug in the IOPC implementation? The presence of two distinct APIs makes usage of the library less clear and confusing.
The library itself remained as a monolithic block with only signs of library configurability. Many parameters are defined as pre-processor macros, enabling other options (like adding a new database driver) results in library source code modification and recompilation. The design of the library even makes impossible to use more than one database driver at a time (the currently used implementation of the DatabaseSqlStatements
is stored in a global variable).
Bad design of the program interface. Useful information is hidden in internal structures of the library, these structures are not visible via its API - for example the data retrieved from the MetadataLoader
. The library could implement some kind of reflection API to be able to query the metamodel.
The library cannot be used in multithreaded environment. There are shared state-aware data structures that are reused between persistence layer calls. This prevents to make the library multithreading-friendly without major modifications.
Despite good idea behind the IOPC library, the implementation is deeply flawed, unusable and unmaintainable. For these reasons, the author of this thesis decided not to continue development upon the source code of IOPC.