Object-Oriented Programming Using SOM and DSOM/Making Your Objects Persistent

The Persistence SOM (PSOM) Framework allows you to make all or part of a SOM object persistent. This means you can preserve the state of an object, ever after the process that creates the object terminates. The Framework provides services that allow you to store an object to a file system or another form of persistent store. It also provides services that permit you restore objects from the persistent storage.

Most applications already use some form of persistence. For example, a spreadsheet or a word processor stores user data in a file. The implementation of a spreadsheet typically contains logic that converts the data in the cells into some format that can be written to a file for storage. When an existing spread sheet is loaded, the reverse takes place. The logic converts the external data back to the cell format so the user can work with it.

The PSOM Framework simplifies these operations by providing a uniform interface to storage. A cell in a spreadsheet can be implemented as a persistent object, and its data can be stored and restored, using the services in the PSOM Framework. Any flattening and unflattening of data can be eliminated. Applications can also avoid writing low level I/O routines and therefore become more portable.

PSOM Overview
To use the PSOM Framework, both the object and the client program that accesses the object must be PSOM aware. What doest.his mean? From the object perspective, it must be derived from a special persistent class and the persistent data must be specified explicitly. This is different from the DSOM approach where an object does not have to know in advance whether it is going to be distributed. The PSOM client is typically responsible for managing persistence by controlling when the persistent data is stored or restored.

The PSOM Framework can store and restore objects that are arbitrarily complex. This means that if an object contains pointers to other objects, the PSOM Framework can save and restore those referenced objects as well. In a hierarchy of objects, the client can control whether or not an object should be stored or restored with its children.

It should be noted that OMG is in the process of releasing a Persistence Service Specification. The goal of the Persistence Service Specification is to provide common interfaces to the mechanisms used for retaining and managing the persistent state of objects. It is expected that the next release of PSOM will comply with the OMG Persistence Service Specification. It is quite likely that the interfaces described in this chapter will change with the next release. However, the concepts behind them should remain the same. More on this can be found in Chapter 10.

The development of a PSOM application involves the following steps. The following sections provide details on each of the above steps.
 * 1) Define the interface for your persistent objects, and write code to implement your objects.
 * 2) Create a client program that accesses and manages the objects.
 * 3) Build your classes and register them with the Interface Repository.
 * 4) Run the client program.

Define and Implement a Persistent Object
To create a persistent object, we must first create a class that is derived from the SOMPPersistentObject class. The parent class for SOMPPersistentObject class is SOMObject. Therefore, your persistent object is still a SOM object, only it can potentially become persistent because it is derived from the SOMPPersistentObject class.

All data that is to be stored persistently must be declared as an attribute of CORBA type. The persistent modifier must be set for each of these attributes in the implementation section of the IDL.

The following Person class is potentially persistent and contains two persistent and one non-persistent attributes. The SOMPPersistentObject class provides the somplsDirty method, which is called by the PSOM Framework, to determine if an object has been changed. The PSOM Framework is optimized so it will only write out changed objects. Since the default implementation of somplsDirty always returns TRUE, it is typically redefined by subclasses so that they can indicate whether an object has been changed. The implementation can invoke the sompGetDirty method to determine if the object has been changed. An example of overriding this method is given in the Persistent Calendar application in Section 6.6 on page 148.

Client Program
Although not absolutely necessary, the storing and restoring of persistent objects is normally controlled by the client of the objects, so the client can decide when to store or restore the persistent state. To save and restore persistent objects, a client program must initialize the PSOM Framework, manage the assignment of persistent ID to the persistent object, and invoke the save or the restore methods.

PSOM Initialization
Every PSOM client must include the file  (or  if using the C bindings). This file includes the constants, global variables, and run-time interfaces that are used by PSOM. The client also has to initialize the DSOM Framework by creating a Persistent Storage Manager object. The Persistent Storage Manager provides the interfaces for clients to save and restore persistent objects. The Persistent Storage Manager is created by instantiating the SOMPPersistentStorageMgr class using the SOMPPersistentStorageMgrNewO macro or the new operator.

There is only one instance of a Persistent Storage Manager object for a process. Therefore, it does not matter how many times you invoke new on the SOMPPersistentStorageMgr class. It is quite normal to invoke new on the SOMPPersistentStorageMgr class locally in each procedure instead of passing the pointer to this object around. You must not free the Persistent Storage Manager object. If you do, the PSOM Framework will forget about previously initialized and restored objects.

A typical implementation of PSOM initialization is presented below: main {    Environment                 *ev; SOMPPersistentStorageMgr   *psm; ev = SOM_CreatelocalEnVJronment;   // create and initialize an Environment structure psm =new SOMPPersistentStorageMgr; // initialize Persistent Storage Manager ...    SOM_DestroylocalEnvironment(ev);      //Free Environment structure }
 * 1) include 

The Persistent ID
In order for a persistent object to be saved or restored, it must be assigned an ID. The ID you assign to a persistent object tells the Framework how and where to store the object. The persistent ID is an object with a string value. The string has the following format. :: The first part of the string identifies what data store the object will be stored in. The second part of the string is a name that is understandable to the data store specified in the first part. The third part is an offset that identifies the object within a group of objects. An example of a persistent ID string is given below: SOMPAscii: \\sample\\test.dat:O The first part of the string "SOMPAscii" identifies that the object is to be stored in a file using ASCII format. The second part, "\\sample\\test.dat", is a file name that indicates the file in which the object will be stored. The third part of the string, "O", is a number that identifies the object within the file where it is stored.

The PSOM Framework provides two 110 Group Manager classes. You can either use the SOMPAscii 110 Group Manager to store data in a ASCII format, or you can use the SOMPBinary 110 Group Manager to store data in a binary format. You can choose which format by setting the first part of the persistent ID string accordingly. The following persistent ID indicates the object that is assigned to it will be stored using binary format. SOMPBinary:\\sample\\test.dat:O The PSOM Framework is available on both AIX and OS/2. The notation for specifying the file name is different on the two systems. The file name that is specified above uses the OS/2 notation. If the same string ID is to be created on AIX, the slashes need to be modified to the following. SOMPAscii:/sample/test.dat:O

Assigning a Persistent ID to a Persistent Object
Typically, a client will create a persistent ID, initialize its string ID, and assign it to the object. The SOMPPersistentld class is responsible for creating the persistent ID and provides methods to initialize the string value in the persistent ID. The SOMPPersistentObject class provides methods to assign a persistent ID to a persistent object. Since all persistent objects must be derived from SOMPPersistentObject, it follows that every persistent object supports the methods for setting its persistent ID.

The SOMPPersistentld class provides the method somutSetid String for setting the string value for a persistent ID. The following code segment shows how to create a persistent ID and assigns the string "SOMPAscii:\\sample\\test.dat:O" to it: SOMPPersistentld *pid; pid = new SOMPPersistentld; pid->somutSetldString( ev, "SOMPAscii:\\sample\\test.dat:O"); The SOMPPersistentObject class provides three methods for assigning a persistent ID to a persistent object. You can assign a persistent object to a specific ID, to a system-generated ID, or to an ID that is near some other specified object. The three methods are sompinitGivenId, sompinitNextAvail, and somplnitNearObject. The choice depends on how much control the client needs over the storage of the object. The client-assigned ID provides the most control while the system-generated ID might be more convenient in other cases.

The somplnitGlvenld Method
The sompinitGivenId method assigns a persistent object to a specific persistent ID. The syntax for the method in C is given below: _sompInitGivenId(persistentObject,    // pointer to persistent object                  env,                  // pointer to persistent ID object                  pid);                 // pointer to persistent ID object The parameter persistentObject is a pointer to the persistent object to which the specified persistent ID is to be assigned. The parameterpid is a pointer to a persistent ID object.

Below is bow you would use the somplnitGivenid method to assign a persistent ID. The class Document is derived from the SOMPPersistentObject class. Document *doc; SOMPPersistentld •pid; pid =new SOMPPersistentId; pid->somutSetIdString(ev, "SOMPAscii:\\sample\\doc .dat :O"); doc = new Document; doc ->sompInitGiven Id(ev,pid); The object doc is assigned the persistent ID whose value is "SOMPAscii:\\sample\doc.dat:O". This means that if a store operation is invoked on the doc object, it will be stored in the ASCII file "\sample\ doc.dat".

The somplnitNextAvoll Method
The somplnitNextAvail method assigns a system-generated ID to a persistent object. The ID must be generated using the SOMPIdAssigner class. The SOMPIdAssignerClass implements a method sompGetSystemAssignedId, which generates an ID, using a simple algorithm based on a path name and a file name. If the environment variable SOMP_PERSIST is set, it will be used as the path name. Otherwise, it will default to the current directory. The file name is the string "p" concatenated with a seven digit hex number. The number is read from a file called "somplast.id" which is updated everytime with the last generated ID. The offset portion of the persistent ID is always set to 0. You can change the default algorithm by subclassing from SOMPIdAssigner and override sompGetSystem.AssignedId.

The syntax for the somplnitNextAvail method is given below: _sompInitNextAvail(persistentObject,   //pointer to persistent object                    env,                 //pointer to Environment structure                    genPid);             //pointer to the next available PIO object The parameter persistentObject is a pointer to the persistent object to which the specified persistent ID is to be assigned. The parameter genPid is a pointer to an object of class SOMPidAssigner, which represents the next available persistent ID.

Here is how you would use the sompInitNextAvail method to assign a persistent ID to the above Document class: Document *doc; SOMPIdAssigner *pid; pid =new SOMPldAssigner; doc = new Document; doc->somplnitNextAvail( ev,pid); Assuming the environment variable SOMP PERSIST is not set and the file "somplast.id" does not exist yet, the ID that is generated above would be: SOMPAscii:.\\pOOOOOOO:O

And the file "somplast.id" is created in the current directory with the following entry: 0000001

The somplnitNearObject Method
The somplnitNearObject method gives a specified persistent object a persistent ID that is nearby the ID of another specified persistent object. This method is useful when you have composite objects you want to store near to the root object. You can initalize your parent object using either of the above methods and then use somplnitNearObject to initialize the children objects. The syntax for the method in C is given below: _somplnitNearObject(persistentObject,  // pointer to persistent object                     env,                // pointer to Environment structure                     nearObj);           // pointer to nearby persistent object The parameter persistentObject is a pointer to the persistent object to which a persistent ID is to be assigned. The parameter, nearObj,is a pointer to another persistent object with a persistent ID.

The following shows how to use sompInitNearObject to set the persistent ID for the doc2 object. Document           *doc1, *doc2; SOMP PersistentId  *pid; pid = new SOMPPersistentId; pid->somutSetIdString(ev, "SOMPAscii:\\sample\\doc.dat:O");

doc1 =new Document: doc2 =new Document;

doc1->sompInitGivenld(ev,pid); doc2->sompInitNear0bject(ev, doc1);    //set doc2 persistent ID to be near doc1 The sompStoreObjectWithoutChildren method stores a persistent object but does not store any of the persistent objects to which it points. It has the same signature as sompStoreObject.

Saving a Persistent Object
Once you have assigned a persistent ID to each persistent object you want to store, you can invoke the store method from the Persistent Storage Manager to store the object. There are two store methods: sompStoreObject and sompStoreObjectWithoutChildren. The sompStoreObject method stores a persistent object and all of its persistent children. The syntax for the method in C is presented below: _sompStoreObject(psm,                //pointer to Persistent Storage Manager                  env,                 //pointer to Environment Structure                  persistentObject);   //pointer to the persistent object being stored

The sompStoreObjectWithoutChildren method stores a persistent object but does not store any of the persistent objects to which it points. It has the same signature as sompStoreObject.

Restoring a Persistent Object
To restore an object you stored using the above methods, you need to know the persistent ID for the object. The Persistent Storage Manager provides the sompRestoreObject and the SompRestoreObjectWithoutChildren methods for restoring objects. The sompRestoreObject method restores a persistent object and all of its persistent children. The syntax for the method in C follows: persistentObject = _sompRestoreObject(psm, // pointer to Persistent Storage Manager                                       env,  // pointer to Environment Structure                                       pid); // pointer to PID object The sompRestoreObjectWithoutChildren method restores a persistent object but does not restore any of the persistent objects to which it points. It has the same signature as sompRestoreObject.

Building and Registering the Classes
Building a PSOM application is very much like a regular SOM application. You compile your classes and your client program. Unlike DSOM, it is not mandatory that you build a DLL for your classes. If you want to build a DLL, follow the steps in "Creating a DLL for SOM Classes" on page 79.

You need to compile your PSOM classes into the Interface Repository. The PSOM Framework uses the Interface Repository to find out things such as whether a class is persistent and which attributes are persistent. The Interface Repository is described in more detail in Chapter 8, Working with the Interface Repository.

Execution
Make sure the environment variable SOMIR points to the Interface Repository that contains the persistent classes. The PSOM application then can be started.

A Distributed and Persistent Calendar
In this section, we will extend the calendar application that we built in the last chapter by adding persistence to it. This means that you can preserve your calendar data even after the termination of the PLANNER program. The user interface remains the same. When you exit the PLANNER program, the activities that you added to or deleted from your calendar will be saved. When you invoke the PLANNER program again for a calendar, the calendar items that you previously added for that calendar will be displayed.

High-Level Design
For the most part, the three classes (WorkItem, Day, and CalendarDir) that made up our calendar DLL remain the same. Modifications have to be made to make the objects persistent.

Our calendar objects were created in a server process that is separated from our client PLANNER process through the use of the DSOM runtime. Therefore, the management of the persistent calendar objects must be handled by the server process. Recall that the SOMDServer class is the base class that defines and implements methods for managing objects in a DSOM server process. We introduce a new server class, PersistentServer, by subclassing from SOMDServer. The PersistentServer class provides methods to manage persistent objects. A new DLL called perserv.dll is created to contain this class.

Implementation Details
This section will provide the details of the design and implementation of each of the components in our new version of the calendar application. The changes made to previous IDLs, or code, are shown in bold.

The WorkItem Class
The IDL for the WorkItem class is given in Figure 6.1. The class is now derived from SOMPPersistentObject so its objects can be persistent. The attributes startTime, endTime, and task are made persistent. The method sompIsDirty is redefined. It invokes the sompGetDirty method to determine if the object has been changed since it was last written out. 
 * 1) ifndef workitem_idl
 * 2) define workitem_idl


 * 1) include 

interface WorkItem : SOMPPersistentObject { attribute string startTime; attribute string endTime; attribute string task;

void mkEntry(in string sTime, in string eTime, in string taskDesc);

#ifdef __SOMIDL__ implementation {   releaseorder : _get_startTime, _set_startTime, _get_endTime, _set_endTime, _get_task, _set_task, mkEntry;

somInit:    override; somUninit:  override; sompIsDirty: override;

dllname = "calendar.dll";

startTime: persistent; endTime:  persistent; task:     persistent; }; #endif };  Figure 6.1 The Work.Item IDL
 * 1) endif

The C++ code which implements the methods for the WorkItem class is given In Figure 6.2. Note that the somlnit and the somUninit methods have been modified to call the initialization and the un-initialization method from the SOMPPersistentObject class.  /* * This file was generated by the SOM Compiler and Emitter Framework. * Generated using: *     SOM Emitter emitxtm: somc/smmain.c */


 * 1) define WorkItem_Class_Source
 * 2) include 

SOM_Scope void SOMLINK mkEntry(WorkItem *somSelf, Environment *ev,                                 string sTime, string eTime, string taskDesc) {   WorkItemData *somThis = WorkItemGetData(somSelf); WorkItemMethodDebug("WorkItem","mkEntry");

if (somThis->startTime) SOMFree(somThis->startTime); somThis->startTime = (string)SOMMalloc(strlen(sTime)+1); strcpy (somThis->startTime, sTime); if (somThis->endTime) SOMFree(somThis->endTime); somThis->endTime = (string)SOMMalloc(strlen(eTime)+1); strcpy (somThis->endTime, eTime); if (somThis->task) SOMFree(somThis->task); somThis->task = (string)SOMMalloc(strlen(taskDesc)+1); strcpy (somThis->task, taskDesc); }

SOM_Scope void SOMLINK somInit(WorkItem *somSelf) {   WorkItemData *somThis = WorkItemGetData(somSelf); WorkItemMethodDebug("WorkItem","somInit");

WorkItem_parent_SOMPPersistentObject_somInit(somSelf);

somThis->startTime = NULL; somThis->endTime  = NULL; somThis->task     = NULL; }

SOM_Scope void SOMLINK somUninit(WorkItem *somSelf) {   WorkItemData *somThis = WorkItemGetData(somSelf); WorkItemMethodDebug("WorkItem","somUninit");

if (somThis->startTime) SOMFree(somThis->startTime); if (somThis->endTime) SOMFree(somThis->endTime); if (somThis->task) SOMFree(somThis->task);

WorkItem_parent_SOMPPersistentObject_somUninit(somSelf); }

SOM_Scope boolean SOMLINK sompIsDirty(WorkItem *somSelf,  Environment *ev) {   WorkItemData *somThis = WorkItemGetData(somSelf); WorkItemMethodDebug("WorkItem","sompIsDirty");

return (somSelf->sompGetDirty(ev)); }  Figure 6.2 continued

The Day Class
The IDL for the Day class is given in Figure 6.3. The class is now derived from SOMPPersistentObject so its objects can be persistent. The attributes date and workList are made persistent. The method sompIsDirty is redefined to determine if the object has been changed since it was last written out. 
 * 1) ifndef day_idl
 * 2) define day_idl


 * 1) include 

interface WorkItem; interface Day : SOMPPersistentObject { const unsigned long MAXITEM = 50; attribute long date; attribute sequence workList;

short book(in WorkItem entry); short removeItem(in string start, in string end, in string desc);

#ifdef __SOMIDL__ implementation {   releaseorder : _get_date, _set_date, _get_workList, _set_workList, book, removeItem;

somInit:    override; somUninit:  override; sompIsDirty: override;

dllname = "calendar.dll";

date:    persistent; workList: persistent; }; #endif };  Figure 6.3 The Day IDL
 * 1) endif

The C++ code that implements the methods for the Day class is given in Figure 6.4 (p. 154). The book method invokes the sompSetDirty method to mark the current Day object as dirty. It also invokes the sompInitNearObject method on the WorkItem object that is being added to the workList and assigns it a persistent ID that is near to the persistent ID of the Day object. The remove method invokes the sompSetDirty method to mark the current Day object as dirty when an item is being removed from the workList. The somlnit and the somUninit methods have been modified similar to the Workltem class.  /* * This file was generated by the SOM Compiler and Emitter Framework. * Generated using: *     SOM Emitter emitxtm: somc/smmain.c */


 * 1) define Day_Class_Source
 * 2) include <day.xih>
 * 3) include <workitem.xh>
 * 4) include <string.h>

SOM_Scope short SOMLINK book(Day *somSelf,  Environment *ev,                              WorkItem* entry) {   DayData *somThis = DayGetData(somSelf); short rc; DayMethodDebug("Day","book");

if (sequenceLength(somThis->workList) < sequenceMaximum(somThis->workList)) {     sequenceElement(somThis->workList,                      sequenceLength(somThis->workList)) = entry; sequenceLength(somThis->workList)++;

somSelf->sompSetDirty(ev); entry->sompInitNearObject(ev, somSelf);

rc = 0L; }   else rc = -1L;

return rc; }

SOM_Scope short SOMLINK removeItem(Day *somSelf,  Environment *ev,                                string start, string end, string desc) {   DayData *somThis = DayGetData(somSelf); short   i;    WorkItem *item; DayMethodDebug("Day","removeItem");

for (i=0; i < sequenceLength(somThis->workList); i++ ) {     item = sequenceElement(somThis->workList,i);

if ( (strcmp(start, item->_get_startTime(ev)) == 0) &&          (strcmp(end, item->_get_endTime(ev)) == 0) &&           (strcmp(desc, item->_get_task(ev)) == 0) ) {        sequenceLength(somThis->workList)--; for (i; i < sequenceLength(somThis->workList); i++) {          sequenceElement(somThis->workList,i) = sequenceElement(somThis->workList, i+1); }

somSelf->sompSetDirty(ev); return 0; }   }    return -1L;       // item not found }

SOM_Scope void SOMLINK somInit(Day *somSelf) {   DayData *somThis = DayGetData(somSelf); DayMethodDebug("Day","somInit");

Day_parent_SOMPPersistentObject_somInit(somSelf);

sequenceMaximum(somThis->workList) = MAXITEM; sequenceLength(somThis->workList) = 0; somThis->workList._buffer = (WorkItem**) SOMMalloc(sizeof (WorkItem *) * MAXITEM); }

SOM_Scope void SOMLINK somUninit(Day *somSelf) {   DayData *somThis = DayGetData(somSelf); DayMethodDebug("Day","somUninit");

if (somThis->workList._buffer) SOMFree(somThis->workList._buffer);

Day_parent_SOMPPersistentObject_somUninit(somSelf); }

SOM_Scope boolean SOMLINK sompIsDirty(Day *somSelf,  Environment *ev) {   DayData *somThis = DayGetData(somSelf); DayMethodDebug("Day","sompIsDirty");

return (somSelf->sompGetDirty(ev)); } </PRE> Figure 6.4 The Day class implementation

The CalendarDir Class
The IDL for the CalendarDir class is given in Figure 6.5. Similar to the above classes, the CalendarDir class is derived from SOMPPersistentObject and the attribute weekList is made persistent. The method somplsDirty is being redefined to determine if the object bas been changed since it was last written out. 
 * 1) ifndef caldir_idl
 * 2) define caldir_idl


 * 1) include <po.idl>

interface Day; interface CalendarDir : SOMPPersistentObject { const unsigned long MAXDAY = 7;

attribute sequence<Day, MAXDAY> weekList;

long addDay(in short daynum, in Day entry); Day getDay(in short daynum);

#ifdef __SOMIDL__ implementation {   releaseorder : _get_weekList, _set_weekList, addDay, getDay;

somInit:    override; somUninit:  override; sompIsDirty: override;

dllname = "calendar.dll";

weekList: persistent; }; #endif }; </PRE> Figure 6.5 The CalendarDir IDL
 * 1) endif

The C++ code that implements the methods for the CalendarDir class is given in Figure 6.6. The addDay method invokes the sompSetDirty method to mark the current CalendarDir object as dirty. It also invokes the somplnitNearObject method on the Day object that is being added to the weekList and assigns it a persistent ID that is near to the persistent ID of the CalendarDir object. The somlnit and the somUninit methods have been modified similar to the Workltem class and the Day class.  /* * This file was generated by the SOM Compiler and Emitter Framework. * Generated using: *     SOM Emitter emitxtm: somc/smmain.c */


 * 1) define CalendarDir_Class_Source
 * 2) include <caldir.xih>
 * 3) include "day.xh"
 * 4) include <stdio.h>

SOM_Scope long SOMLINK addDay(CalendarDir *somSelf,  Environment *ev,                                short daynum, Day* entry) {   CalendarDirData *somThis = CalendarDirGetData(somSelf); long           rc; CalendarDirMethodDebug("CalendarDir","addDay");

if (sequenceLength(somThis->weekList) < sequenceMaximum(somThis->weekList)) {     sequenceElement(somThis->weekList,                       sequenceLength(somThis->weekList)) = entry; sequenceLength(somThis->weekList)++;

somSelf->sompSetDirty(ev); entry->sompInitNearObject(ev, somSelf);

rc = 0L; }   else rc = -1L;

return rc; }

SOM_Scope Day* SOMLINK getDay(CalendarDir *somSelf,  Environment *ev,                                short daynum) {   CalendarDirData *somThis = CalendarDirGetData(somSelf); CalendarDirMethodDebug("CalendarDir","getDay");

return ( sequenceElement(somThis->weekList, daynum) ); }

SOM_Scope void SOMLINK somInit(CalendarDir *somSelf) {   CalendarDirData *somThis = CalendarDirGetData(somSelf); CalendarDirMethodDebug("CalendarDir","somInit");

CalendarDir_parent_SOMPPersistentObject_somInit(somSelf);

sequenceMaximum(somThis->weekList) = MAXDAY; sequenceLength(somThis->weekList) = 0; somThis->weekList._buffer = (Day**) SOMMalloc(sizeof (Day *) * MAXDAY);

}

SOM_Scope void SOMLINK somUninit(CalendarDir *somSelf) {   CalendarDirData *somThis = CalendarDirGetData(somSelf); CalendarDirMethodDebug("CalendarDir","somUninit");

if (somThis->weekList._buffer) SOMFree(somThis->weekList._buffer);

CalendarDir_parent_SOMPPersistentObject_somUninit(somSelf); }

SOM_Scope boolean SOMLINK sompIsDirty(CalendarDir *somSelf,                                         Environment *ev) {   CalendarDirData *somThis = CalendarDirGetData(somSelf); CalendarDirMethodDebug("CalendarDir","sompIsDirty");

return (somSelf->sompGetDirty(ev)); } </PRE> Figure 6.6 The CalendirDir class implementation

The PersistentServer Class
The IDL for the PersistentServer class is presented in Figure 6.7. It introduces three new methods and overrides one method. 
 * The getPerStorMgr method creates the Persistence Storage Manager object for this process and returns the pointer to it. Clients that create remote persistent objects using this class can store them using the handle returned by this method.
 * The objectExists method checks to see if the specified persistent object exists in the persistent store. If it does, it returns TRUE. Otherwise, it returns FALSE.
 * The restoreObjectFromld method restores a persistent object and all its children from the object string ID.
 * The somdCreateObj method is overridden to create a remote persistent object. It first creates the object by calling the parent implementation. It then checks to see if a string ID is supplied in the hint field. If a string ID is supplied, a persistent ID is created and initialized with the string ID. The persistent ID is then assigned to the persistent object.
 * 1) ifndef perserver_idl
 * 2) define perserver_idl


 * 1) include <somobj.idl>
 * 2) include <somdserv.idl>
 * 3) include <somdtype.idl>
 * 4) include <snglicls.idl>

interface PersistentServer : SOMDServer { // Return the Persistent Storage Manager in this process SOMObject getPerStorMgr;

// Check to see if the persistent object exists boolean objectExists(in string id);

// Restore a persistent object from its string ID SOMObject restoreObjectFromId(in string id);

#ifdef __SOMIDL__ implementation {   releaseorder: getPerStorMgr, objectExists, restoreObjectFromId;

somdCreateObj : override; dllname = "perserv.dll"; metaclass = SOMMSingleInstance; }; #endif }; </PRE> Figure 6.7 The PersistentServer IDL
 * 1) endif

The C++ code that implements the methods for the PersistentServer class is given in Figure 6.8. The initialization function SOMinitModule is included in the file for dynamic loading.  /* * This file was generated by the SOM Compiler and Emitter Framework. * Generated using: *     SOM Emitter emitxtm: 2.7 */


 * 1) define PersistentServer_Class_Source
 * 2) define SOMMSingleInstance_Class_Source
 * 3) include <perserv.xih>
 * 4) include <somp.xh>
 * 5) include <stdio.h>

#pragma linkage(SOMInitModule,system)
 * 1) ifdef __IBMC__
 * 1) endif

SOMEXTERN void SOMLINK SOMInitModule(long majorVersion,                                                  long minorVersion,                                                   string className) {                                                                      SOM_IgnoreWarning(majorVersion); SOM_IgnoreWarning(minorVersion); SOM_IgnoreWarning(className); PersistentServerNewClass(PersistentServer_MajorVersion,                            PersistentServer_MinorVersion); }

/* * Check to see if the persistent object exists */

SOM_Scope SOMObject* SOMLINK getPerStorMgr(PersistentServer *somSelf,                                              Environment *ev) {   /* PersistentServerData *somThis = PersistentServerGetData(somSelf); */ SOMPPersistentStorageMgr *psm; PersistentServerMethodDebug("PersistentServer","getPerStorMgr");

psm = new SOMPPersistentStorageMgr; return psm; }

/* * Restore a persistent object from its string ID */

SOM_Scope boolean SOMLINK objectExists(PersistentServer *somSelf,                                         Environment *ev, string id) {   /* PersistentServerData *somThis = PersistentServerGetData(somSelf); */ SOMPPersistentStorageMgr *psm; SOMPPersistentId        *pid; boolean                 rc; PersistentServerMethodDebug("PersistentServer","objectExists");

psm = new SOMPPersistentStorageMgr; pid = new SOMPPersistentId; pid->somutSetIdString(ev, id); if ( psm->sompObjectExists(ev, pid) ) {      rc = TRUE; }   else {      rc = FALSE; }   delete pid;

return rc; }

SOM_Scope SOMObject* SOMLINK restoreObjectFromId(PersistentServer *somSelf,                                                   Environment *ev,                                                   string id) {   /* PersistentServerData *somThis = PersistentServerGetData(somSelf); */ SOMPPersistentStorageMgr *psm; SOMObject               *obj; PersistentServerMethodDebug("PersistentServer","restoreObjectFromId");

psm = new SOMPPersistentStorageMgr;

obj = psm->sompRestoreObjectFromIdString(ev,id);

return obj; }

SOM_Scope SOMObject* SOMLINK somdCreateObj(PersistentServer *somSelf,                                             Environment *ev,                                             Identifier objclass,                                             string hints) {   /* PersistentServerData *somThis = PersistentServerGetData(somSelf); */ SOMPPersistentObject *obj; SOMPPersistentId    *pid;

PersistentServerMethodDebug("PersistentServer","somdCreateObj");

obj = (SOMPPersistentObject *) PersistentServer_parent_SOMDServer_somdCreateObj(somSelf,                                                             ev,                                                              objclass,                                                              NULL); if (hints != NULL) {     //***************************************      // Assign persistent ID to object //***************************************     pid = new SOMPPersistentId; pid->somutSetIdString(ev, hints); obj->sompInitGivenId(ev, pid); }

return obj; } </PRE> Figure 6.8 The PersistentServer class implementation

The GUI Client
The PLANNER program is now modified to work with the Persistent Framework. The listing for planwin.hpp is given in Figure 6.9. The changes are highlighted in bold. 
 * 1) ifndef PLANWIN_HPP
 * 2) define PLANWIN_HPP


 * 1) include <iframe.hpp>
 * 2) include <icmdhdr.hpp>
 * 3) include <imcelcv.hpp>
 * 4) include <ilistbox.hpp>
 * 5) include <iselhdr.hpp>
 * 6) include <istring.hpp>
 * 7) include <istattxt.hpp>
 * 8) include <imenubar.hpp>
 * 9) include <ispinbt.hpp>
 * 10) include <ientryfd.hpp>


 * 1) include <somd.xh>
 * 2) include <somp.xh>


 * 1) include "week.h"
 * 2) include "workitem.xh"
 * 3) include "day.xh"
 * 4) include "caldir.xh"
 * 5) include "perserv.xh"

class DayPlannerWindow : public IFrameWindow, public ICommandHandler, public ISelectHandler { public: DayPlannerWindow (unsigned long windowId, char *name); ~DayPlannerWindow;

protected: virtual Boolean command(ICommandEvent& cmdevt); virtual Boolean selected(IControlEvent& evt);

private: setupClient; setupData; short findProxy; short findPersist; externalizeProxy; showItems(short day); book(char *start, char *end, char *desc);

IEntryField *descT; IStaticText *weekday, *start, *end, *desc; ISpinButton *startT, *endT;

IMultiCellCanvas *mc; IMenuBar        *menuBar; short           menuFlag; short           createFile;

IListBox        *listBox; IListBox::Cursor *cursor;

// DSOM related variables PersistentServer *server;           // use Persistent Server char            *serverName; Environment     *ev; CalendarDir     *curDirEntry; Day             *curDay;

// PSOM related variables SOMPPersistentStorageMgr *psm; char                    *pidString;     // persistent ID string };

</PRE> Figure 6.9 The DayPlannerWindow header file
 * 1) endif

The following summarizes what the new member functions do and the changes to existing member functions.


 * The setupData function is modified to initialize both the DSOM and the PSOM environment. An instance of the Persistence Storage Manager is created at the specified calendar server. There are two forms of object sharing in our new application. The first form is through an existing object reference, which is what we had before. The second form is through persistent data. We first call the findProxy function to determine if there is an existing object reference to the CalendarDir object that is managed by this server. If an object reference a1ready exists, it will be used. Otherwise, we call the findPersist function to determine if there is a persistent object for this calendar. If a persistent form exists, it will be restored. If neither the object reference nor the persistent object exists, a new CalendarDir object is created, and the externalizeProxy function is called to externalize the object reference.

A naming convention is used to determine the persistent ID string for the CalendarDir object. If the calendar server is "pChris", then the persistent ID will be "SOMAscii:. \pChris.dat:O". This means that the persis tent calendar objects will be stored in the file "pChris.dat" in the current directory.
 * The findPersist function is a new function. It checks to see if the persistent objects for this calendar already exist. If the persistent objects already exist, they will be restored, and the extemalizeProxy function is called to externalize the object reference to the CalendarDir object.

cleanup. The partial listing for the source file planwin.cpp appears in Figure 6.10. Any code that has been changed or added is shown. Member functions that have not been changed are not listed. A complete listing is included on the diskette. 
 * The extemalizeProxy function is a new function. It converts the object reference to the CalendarDir object into a string ID. The string ID is written to a file in order to allow sharing of the Calendar Dir object.
 * The destructor is modified to save the persistent objects in addition to performing
 * 1) include <ifont.hpp>
 * 2) include <ititle.hpp>
 * 3) include <iostream.h>
 * 4) include <fstream.h>
 * 5) include <imsgbox.hpp>


 * 1) include <stdio.h>
 * 2) include "planwin.hpp"

void main(int argc, char *argv[], char *envp[]) { IString filename; if (argc == 2) {    filename = argv[1]; } else {    filename = "pChris";   // default to my persistent calendar } DayPlannerWindow mainWindow(WND_MAIN, filename); IApplication::current.run; }

DayPlannerWindow :: DayPlannerWindow(unsigned long windowId,                                    char *name) : IFrameWindow (IFrameWindow::defaultStyle, windowId) , serverName(name) , createFile(0) {  IString title(" Weekly Calendar");

ICommandHandler::handleEventsFor(this);

title = serverName + title; ITitle(this, title);

menuBar = new IMenuBar(WND_MAIN,this); menuBar->checkItem(MI_BASE); menuFlag = MI_BASE;

setupClient; setupData;

if ( IWindow::desktopWindow->size > ISize(1000,700) ) {     sizeTo(ISize(460,330)); }  else  // VGA {     sizeTo(ISize(360,240)); }

setFocus; show; }

DayPlannerWindow :: setupClient { mc = new IMultiCellCanvas(WND_CANVAS, this, this); mc->setAutoDeleteObject; setClient(mc);

weekday = new IStaticText(WND_TEXT, mc, mc); weekday->setAutoDeleteObject; weekday->setText("Monday");

start = new IStaticText(WND_TEXT, mc, mc); start->setAutoDeleteObject; start->setText("Start ");

startT = new ISpinButton(WND_START,mc,mc); startT->setAutoDeleteObject; startT->setInputType(ISpinButton::numeric); startT->setRange(IRange(1,24)); startT->setCurrent(8); startT->setLimit(2); startT->readOnly;

end = new IStaticText(WND_TEXT, mc, mc); end->setAutoDeleteObject; end->setText("End   ");

endT = new ISpinButton(WND_END,mc,mc); endT->setAutoDeleteObject; endT->setInputType(ISpinButton::numeric); endT->setRange(IRange(1,24)); endT->setCurrent(6); endT->setLimit(2); endT->readOnly;

desc = new IStaticText(WND_TEXT, mc, mc); desc->setAutoDeleteObject; desc->setText("Description");

descT = new IEntryField(WND_DESC, mc, mc); descT->setAutoDeleteObject;

listBox = new IListBox(WND_LISTBOX,mc,mc,                        IRectangle,                         IListBox::defaultStyle |                         IControl::tabStop); listBox->setAutoDeleteObject;

cursor = new IListBox::Cursor(*listBox); ISelectHandler::handleEventsFor(listBox);

mc->addToCell(weekday,2,2); mc->addToCell(start, 2,4); mc->addToCell(startT, 2,5); mc->addToCell(end,   4,4); mc->addToCell(endT,  4,5); mc->addToCell(desc,  6,4); mc->addToCell(descT, 6,5); mc->addToCell(listBox,2,7,5,1);

mc->setRowHeight(3,2,true); mc->setRowHeight(6,2,true); mc->setRowHeight(8,2,true); mc->setColumnWidth(3,2,true); mc->setColumnWidth(5,2,true); mc->setColumnWidth(7,2,true); }

DayPlannerWindow :: setupData { short i, found; Day  *day; ev = SOM_CreateLocalEnvironment; SOMD_Init(ev);

PersistentServerNewClass(0,0); SOMPPersistentStorageMgrNewClass(0,0);

WorkItemNewClass(0,0); DayNewClass(0,0); CalendarDirNewClass(0,0);

server = (PersistentServer *) SOMD_ObjectMgr->somdFindServerByName(ev,serverName); psm = (SOMPPersistentStorageMgr *) server->getPerStorMgr(ev);

found = findProxy;

if (!found) // no current client yet {   found = findPersist; }

if (!found) {   //************************************************************    // Create a new CalendarDir object and add 7 Days to the list. // Pass pidString as "hints". //************************************************************   curDirEntry = (CalendarDir *) server->somdCreateObj(ev,                                                        "CalendarDir",                                                        pidString); externalizeProxy; for (i=0; i<7; i++ ) {      day = (Day *) server->somdCreateObj(ev, "Day", NULL); day->_set_date(ev,i); curDirEntry->addDay(ev, i, day); } }

// Set current day to Monday and show any existing activities curDay = curDirEntry->getDay(ev,1); showItems(1); }

DayPlannerWindow :: externalizeProxy { string objectId;

objectId = SOMD_ObjectMgr->somdGetIdFromObject(ev,curDirEntry); ofstream outfile(serverName); outfile << objectId; outfile.close; createFile = 1;    // remember this client created the file }

short DayPlannerWindow :: findProxy { ifstream infile(serverName); string  objectId; char    buffer[256]; short   found = 0; if (infile)   // proxy exist {   //***************************************    // restore proxy from its string form //***************************************   objectId = (string) buffer; infile >> objectId; curDirEntry = (CalendarDir *) SOMD_ObjectMgr->somdGetObjectFromId(ev,                                                objectId); return 1; } return 0; }

short DayPlannerWindow :: findPersist { //*************************************************************  // Create string for persistent ID. We will store the // persistent objects in a file using the following convention: //  SOMPAscii:.\\<serverName>.dat:0 //************************************************************* pidString = new char[strlen(serverName) + strlen("SOMPAscii:.\\.dat:0") + 1]; sprintf(pidString, "SOMPAscii:.\\%s.dat:0", serverName);

if ( server->objectExists(ev, pidString) ) {   curDirEntry = (CalendarDir *) server->restoreObjectFromId(ev,                                                              pidString); externalizeProxy; return 1; } else {   return 0; } }

DayPlannerWindow :: ~DayPlannerWindow { short i,j; _IDL_SEQUENCE_WorkItem alist;

// Store all objects psm->sompStoreObject(ev, curDirEntry); if (createFile)    // this client writes proxy to file {    //**************************************     // perform clean up: delete the file //**************************************    IString buffer("erase "); buffer = buffer + serverName; system(buffer);

//**************************************    // Destroy each Day object //**************************************    for (i=0; i<7; i++ ) {      curDay = curDirEntry->getDay(ev,i); alist = curDay->_get_workList(ev); if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50) {        //*********************************************         // Destroy each WorkItem from the Day object //*********************************************        for (j=0; j < sequenceLength(alist) ; j++) {           SOMD_ObjectMgr->somdDestroyObject(ev,                                              sequenceElement(alist,j)); }      }       SOMD_ObjectMgr->somdDestroyObject(ev,curDay); }    // Destroy CalendarDir object SOMD_ObjectMgr->somdDestroyObject(ev, curDirEntry); } else {   //**************************************    // Release the proxy for each Day object //**************************************   for (i=0; i<7; i++ ) {     curDay = curDirEntry->getDay(ev,i); alist = curDay->_get_workList(ev); if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50) {        //**************************************************         // Release proxy for each WorkItem from Day object //*****************************************************        for (j=0; j < sequenceLength(alist) ; j++) {          SOMD_ObjectMgr->somdReleaseObject(ev,                                             sequenceElement(alist,j)); }     }      SOMD_ObjectMgr->somdReleaseObject(ev,curDay); }

// Release CalendarDir proxy SOMD_ObjectMgr->somdReleaseObject(ev,curDirEntry); } // release server proxy SOMD_ObjectMgr->somdReleaseObject(ev, server); SOMD_Uninit(ev); SOM_DestroyLocalEnvironment(ev); }

Boolean DayPlannerWindow :: command(ICommandEvent & cmdEvent) { IMessageBox msgbox(this); switch (cmdEvent.commandId) {   case MI_ADD: if ( !(descT->text.size) ) {        msgbox.show("Enter a description", IMessageBox::okButton); }      else {       IString str; IString pad("0"); IString trial(":00 "); IString blank(" "); IString sstr(startT->value); IString estr(endT->value);

if ( startT->value < 10 ) {         sstr = pad + sstr; }       if ( endT->value < 10 ) {         estr = pad + estr; }       sstr = sstr + trial; estr = estr + trial;

str = sstr + estr + descT->text; listBox->addAscending(str);

book( sstr, estr, descT->text ); }     return true; break; case MI_DEL: if ( cursor->isValid ) {       IString item, startTime, endTime, task;

item = listBox->elementAt(*cursor);

startTime = item.subString(1,6); endTime  = item.subString(7,6); task     = item.subString(13); curDay->removeItem(ev, startTime, endTime, task);

listBox->removeAt(*cursor); }     return true; break; case MI_SUN: weekday->setText("Sunday"); showItems(0); return true;

case MI_MON: weekday->setText("Monday"); showItems(1); return true;

case MI_TUE: weekday->setText("Tuesday"); showItems(2); return true; case MI_WED: weekday->setText("Wednesday"); showItems(3); return true;

case MI_THU: weekday->setText("Thursday"); showItems(4); return true; case MI_FRI: weekday->setText("Friday"); showItems(5); return true; case MI_SAT: weekday->setText("Saturday"); showItems(6); return true;

case MI_QUIT: close; return true; break; } return false; }

Boolean DayPlannerWindow :: selected(IControlEvent & evt) { cursor->setToFirst; return true; }

DayPlannerWindow :: showItems(short day) { _IDL_SEQUENCE_WorkItem alist; short                 i;  IString                str; long                  date;

listBox->removeAll;            // clear list menuBar->uncheckItem(menuFlag);  // uncheck previous day menuBar->checkItem(MI_BASE+day); // check selected day menuFlag = MI_BASE + day;

curDay = curDirEntry->getDay(ev,day); alist = curDay->_get_workList(ev);

if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50) {   for (i=0; i < sequenceLength(alist) ; i++) {     str = ""; str = str + sequenceElement(alist,i)->_get_startTime(ev) + sequenceElement(alist,i)->_get_endTime(ev) + sequenceElement(alist,i)->_get_task(ev);

listBox->addAscending(str); } } }

DayPlannerWindow :: book(char *start, char *end, char *task) {  WorkItem *item;

item = (WorkItem *) server->somdCreateObj(ev,                                            "WorkItem",                                             NULL); item->mkEntry(ev, start, end, task); curDay->book(ev, item); } </PRE> Figure 6.10 The DayPlannerWindow implementation (changed code only)

Observe that we only use one store and one restore operation to write and read all the persistent calendar objects. This is possible because our persistent objects are structured as a hierarchy of objects as shown in Figure 6.11. Furthermore, notice that we assigned an explicit persistent ID to the root object. The remaining persistent IDs were assigned using the sompInitNearObject method where each object is assigned an ID that is near to the previously assigned ID. This technique is very useful for applications that work with a hierarchy of objects that is saved and restored as a whole.



Figure 6.11 Calendar Objects Hierarchy

The Makefile
The makefile, listed in Figure 6.12, has been modified to build the distributed, persistent calendar. The source file that contains the class PersistentServer is perserv.cpp. The source file perserv.def contains the module definition file. It is not listed here but is included on the diskette. These two files are needed to build the perserv DLL.

The four classes are compiled into the Interface Repository file that is designated by the SOMIR environment variable. Two calendar servers, "pChris" and "pKevin", are registered with the Implementation Repository that is designated by the SOMDDIR environment variable. Note that the PersistentServer class is registered as the server class that will manage objects in the "pChris" and "pKevin" servers.

Building the Application
The application is built by invoking NMAKE in a similar manner as before. The file d.somenv.cmd is provided to set up the environment variables for the application.

Running the Application
Start the DSOM daemon as before. > start / f somdd Then start the PLANNER program using the default ("pChris") server. >planner Add some activities to pChris's calendar. Then select Quit to exit the application. The file "pChris.dat" should be created in your current directory. Take a look at it.

Start the PLANNER program again. The activities that were added before will be displayed.

You can try the sequences from the last chapter such as starting a second PLANNER program using the "pChris" server or start the PLANNER program using the "pKevin" server.

Observe the objects are only written out if they have been changed. Compare the time difference between exiting a calendar that has changed objects, versus one that does not have any changed objects.

Summary
Now that our calendar application is both distributed and persistent, what else is missing? You might have noticed that if you have multiple users sharing a calendar, then when one user makes a change to the calendar, the change is not automatically reflected in the other users' views. The other users need to either refresh their displays manually or restart the PLANNER program in order to see the changes. Can we extend this application so that a change is automatically propagated to all the views of the calendar? We will answer this question in the next chapter.  .SUFFIXES: .SUFFIXES: .idl .xih .xh .cpp .obj .def OBJS = workitem.obj day.obj caldir.obj initfunc.obj POBJS = perserv.obj UILIB = dde4muii.lib dde4cci.lib dde4mbsi.lib os2386.lib
 * 1) Need /Ge- to build DLL
 * 1) Need /Ge- to build DLL

all: calendar.dll perserv.dll planner.exe som.ir somdimpl

.cpp.obj: icc /c+ /Ge- -I. $< .idl.xh: sc -sxh $*.idl .idl.xih: sc -sxih $*.idl .idl.cpp: sc -sxc $*.idl .idl.def: sc -sdef $*.idl

initfunc.obj: initfunc.cpp

clnimpl: -regimpl -D -i pChris -regimpl -D -i pKevin

workitem.obj: workitem.xih workitem.xh workitem.cpp workitem.xih: workitem.idl workitem.xh: workitem.idl workitem.cpp: workitem.xih

day.obj: day.xih day.xh day.cpp day.xih: day.idl day.xh: day.idl day.cpp: day.xih

caldir.obj: caldir.xih caldir.xh caldir.cpp caldir.xih: caldir.idl caldir.xh: caldir.idl caldir.cpp: caldir.xih

initfunc.obj: initfunc.cpp

perserv.obj: perserv.xih perserv.xh perserv.cpp perserv.xih: perserv.idl perserv.xh: perserv.idl perserv.cpp: perserv.xih

calendar.dll: $(OBJS) calendar.def icc @<< /Fe"calendar.dll" $(OBJS) calendar.def somtk.lib <<       implib calendar.lib calendar.def
 * 1) Build the Calendar DLL
 * 1) Build the Calendar DLL

perserv.dll: $(POBJS) perserv.def icc @<< /Fe"perserv.dll" $(POBJS) perserv.def somtk.lib <<       implib perserv.lib perserv.def planwin.obj: planwin.cpp planwin.hpp week.h        icc /c+ /Gd+ /Gm+ /Si+ -I. planwin.cpp planner.exe: planwin.obj week.res icc /Fe"planner.exe" planwin.obj /B" /pm:pm /noi" \ $(UILIB) somtk.lib calendar.lib perserv.lib planner.def rc week.res planner.exe
 * 1) Build the Persistent Server DLL
 * 1) Build the Persistent Server DLL
 * 1) Build the executables
 * 1) Build the executables

week.res: week.rc week.h         rc -r week.rc som.ir: workitem.idl day.idl caldir.idl perserv.idl sc -sir -u workitem.idl sc -sir -u day.idl sc -sir -u caldir.idl sc -sir -u perserv.idl somdimpl: workitem.idl day.idl caldir.idl perserv.idl -regimpl -A -i pChris -p $(SOMBASE)\bin\somdsvr.exe -v PersistentServer -regimpl -a -i pChris -c WorkItem -c Day -c CalendarDir -regimpl -L -i pChris -regimpl -A -i pKevin -p $(SOMBASE)\bin\somdsvr.exe -v PersistentServer -regimpl -a -i pKevin -c WorkItem -c Day -c CalendarDir -regimpl -L -i pKevin @echo x > somdimpl
 * 1) Put the IDL descriptions into the Interface Repository
 * 1) Put the IDL descriptions into the Interface Repository
 * 1) Build the DSOM Implementation Repository.
 * 2) Register two servers: pChris and pKevin
 * 1) Register two servers: pChris and pKevin

</PRE> Figure 6.12 The distributed and persistent PLANNER Makefile