Jump to content

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

From EDM2
Revision as of 11:56, 31 May 2018 by Ak120 (talk | contribs)

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.

  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.

The following sections provide details on each of the above steps.

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 dervied 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.

#include <po.idl>
interface Person: SOMPPersistentObject
{
   attribute string name;
   attribute string birthdate: 
   attribute short age;

   #ifdef _SOMIDL_
   implementation
   {
      releaseorder : _get_name, _set_name.
                     _get_birthdate, _set_birthdate,
                     _get_age, _set_age;

      somplsDirty:override;

      name: persistent;
      birthdate: persistent ;
    };
    #end if
} ;
#end if

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.

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 (Workltem, 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.

#ifndef workitem_idl
#define workitem_idl

#include <po.idl>

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
};
#endif

Figure 6.1 The Work.Item IDL

The C++ code which implements the methods for the Workltem class is given In Figure 6.2. Note that the somlnit and the somUninit methods have been modified to call the initalization 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
 */

#define WorkItem_Class_Source
#include <workitem.xih>

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.

#ifndef day_idl
#define day_idl

#include <po.idl>

interface WorkItem;
interface Day : SOMPPersistentObject
{
  const unsigned long MAXITEM = 50;
  attribute long date; 
  attribute sequence<WorkItem,MAXITEM> 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
};
#endif

Figure 6 .3 The Day IDL

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
 */

#define Day_Class_Source
#include <day.xih>
#include <workitem.xh>
#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));
}

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.

#ifndef caldir_idl
#define caldir_idl

#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
};
#endif

Figure 6.5 The CalendarDir IDL

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
 */

#define CalendarDir_Class_Source
#include <caldir.xih>
#include "day.xh"
#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));
}

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.
#ifndef perserver_idl
#define perserver_idl

#include <somobj.idl>
#include <somdserv.idl>
#include <somdtype.idl>
#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
};
#endif

Figure 6.7 The PersistentServer IDL

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
 */

#define PersistentServer_Class_Source
#define SOMMSingleInstance_Class_Source
#include <perserv.xih>
#include <somp.xh>
#include <stdio.h>

#ifdef __IBMC__                                                      
  #pragma linkage(SOMInitModule,system)                              
#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;
}

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.

#ifndef PLANWIN_HPP
#define PLANWIN_HPP

#include <iframe.hpp>            
#include <icmdhdr.hpp>           
#include <imcelcv.hpp>
#include <ilistbox.hpp>
#include <iselhdr.hpp>
#include <istring.hpp>
#include <istattxt.hpp>
#include <imenubar.hpp>
#include <ispinbt.hpp>
#include <ientryfd.hpp>

#include <somd.xh>        
#include <somp.xh>        

#include "week.h"
#include "workitem.xh"       
#include "day.xh"            
#include "caldir.xh"        
#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
}; 

#endif

Figure 6.9 The DayPlannerWindow header file

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.
  • 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 r estored, and the extemalizeProxy function is called to externalize the object reference to the CalendarDir object.

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 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

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.

#include <ifont.hpp>
#include <ititle.hpp>
#include <iostream.h>       
#include <fstream.h>        
#include <imsgbox.hpp>   

#include <stdio.h>
#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);
}

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 perseru.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 perseru 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 
#
# Need /Ge- to build DLL
#
OBJS  = workitem.obj day.obj caldir.obj initfunc.obj
POBJS = perserv.obj 
UILIB = dde4muii.lib dde4cci.lib dde4mbsi.lib os2386.lib

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                             

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

#
# Build the Persistent Server DLL
#
perserv.dll: $(POBJS) perserv.def
        icc @<<                                     
        /Fe"perserv.dll" $(POBJS) perserv.def
        somtk.lib                                   
<<                                                  
        implib perserv.lib perserv.def
                                                    
#
# Build the executables
#
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

week.res: week.rc week.h
          rc -r week.rc
#
# Put the IDL descriptions into the Interface Repository
#
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
#
# Build the DSOM Implementation Repository.
# Register two servers: pChris and pKevin
#
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

Figure 6.12 The distributed and persistent PLANNER Makefile