Object-Oriented Programming Using SOM and DSOM/Using Replicated Objects

From EDM2
Jump to: navigation, search

The Replication SOM (RSOM) Framework allows you to replicate an object in several different processes distributed across a network. Each replica of the object can be updated, and the Framework handles the propagation of the update to all the other replicas. The updates are serialized by the Framework and are propagated from the originating process to the other participants, without the use of secondary storage.

The RSOM Framework can be used to implement "groupware" type applications. For example, using the Replication Framework, a compound document consisting of text, diagrams, bitmaps, and graphics can be worked on collaboratively by multiple concurrent users located at different workstations on a network. Each user can see the changes made by others in the group. Traditionally in a distributed environment, information sharing is achieved through the use of database servers. The Replication Framework provides a new way for sharing information that puts a user in direct contact with other users. Each user sees the changes made by others instantaneously.

Each replica is not aware of how many other replicas there are in the network, or where they are in the network. A participant can join or leave the group at any time, and the replicas in the network will not be aware of it. The Framework can tolerate process or node failure, and provides recovery.

RSOM Overview

To use the RSOM Framework, both the object, and the client program that accesses the object, must be RSOM aware. From the object perspective, it must be derived from a special replication class and provide methods to get and set the complete state of the object. The RSOM client is responsible for initializing the Replication Framework and to set up the main event processing loop for the application.

The development of an RSOM application involves the following steps.

  1. Define the interface for your replicated objects, and write code to implement your objects.
  2. Create a client program that accesses the objects.
  3. Build your classes, and register them with the Interface Repository.
  4. Start TCP/IP.
  5. Run the client program.

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

Define and Implement a Replicated Object

To create a replicated object, we must first create a class derived from the SOMRReplicbl class. SOMRReplicbl is derived from SOMRNameable and SOMRLinearizable. Both SOMRNameable and SOMRLinearizable are subclassed from SOM Object.

Any class that is derived either directly or indirectly from SOMRReplicbl can have groups of object instances that are replicas of each other. For an object to be replicable, a number of rules must be followed. These rules are discussed in the following sections.

Replica Nome

A replicated object must have a name. All objects initialized for replication under the same name are replicas of one another. For example, assume the Document class is replicable. If process 1 creates an object of Document class and sets its name to "overview", and process 2 creates an object of Document class and sets its name to "overview", then these two objects are replicas of each other. Any update made to one replica will be propagated to the other, provided that the set of replication rules are followed.

The SOMRNameable class provides the somrSetObjName and the somrGetObjName methods to set and get the name of an object. The C syntax for the somrSetObjName method is given below:

_somrSetObjNamel(repObject,    // pointer to an object of SOMRNameable
                 env,          // pointer to Environment structure
                 name);        // pointer to the name of the object

The parameter repObject is a pointer to a replicated object to which the specified name is to be assigned. The parameter name is a null-terminated string that contains the name of the object.

Operation Vs. Value Logging

The Replication Framework defines two ways of propagating changes among replicas. They are:

  1. Operation logging-each method that modifies a replica will execute at the site of the other replicas.
  2. Value logging-the change in value of a replica is encoded after a method invocation, and the values of the other replicas are updated to reflect the changes.

The choice between the two depends on how long it takes to compute the new value of an object. For example, if you have a method that performs some very complex calculations, you should compute the result once, and use value logging to transfer the new value to the other replicas. On the other hand, if the method executes quickly, or transferring the results might take longer than having the replicas repeat the execution of the method, then operation logging should be used instead.

The SOMRReplicbl class provides the somrRepInit method to initialize an object for operation or value logging. The C syntax for the somrRepInit method follows:

_somrRepInit( repObject,   //pointer to an object of SOMRReplicbl
                env,       //pointer to Environment structure
                log Type,  //type of logging
                mode);     //read or write mode

The parameter repObject is a pointer to a replicated object. The parameter logType indicates the type of logging used. It can be either 'o' for operation logging or 'v' for value logging. The parameter mode indicates whether or not the object can be written. If the mode is 'w', then the object can be written. If the mode is 'r', then the object can only be read by the Framework.

Replica Lock

A replicated object must obtain a replica lock before updating its data and must release the lock after the update. The SOMRReplicbl class provides different locking methods, depending on whether operation logging or value logging is used.

Operation Logging Lock

If operation logging is used, then a replicated class must invoke the somrLockNlogOp method to acquire a lock on the current replica, before the update is made. When the update is completed, the object must invoke the somrReleaseNPropagateOperation method to release the lock. In the case when the object wants to abort an update operation, it must invoke the somrReleaseLockNAbortOp method.

The C syntax for the somrLockNlogOp method follows:

_sornrlockNlogOp(repObject,    //pointer to an object of SOMRReplicbl
                 env,          //pointer to Environment structure
                 class Name,   //name of class
                 method Name,  //name of method
                 ap);          //arguments

The parameter repObject is a pointer to a replicated object. The parameter className is the class name of the replicated object. The parameter methodName is the name of the method that performs the update. The parameter ap is a pointer to a va_list that specifies the arguments for the method.

The C syntax for somrReleaseNPropagateOperation and somrReleaseLockNAbortOp follow.

_somrReleaseNPropagateOperation(repObject. //pointer to an object of SOMRReplicbl
                                env);      //pointer to Environment structure

_somrReleaselockNAbortOp(repObject,        //pointer to an object of SOMRReplicbl
                         env):             //pointer to Environment structure

Value logging lock

If value logging is used, then a replicated class must invoke the somrLock method to acquire a lock on the current replica before the update is made. When the update is completed, the object must invoke the somrReleaseNPropagateUpdate method to release the lock. In the case when the object wants to abort an update operation, it must invoke the somrReleaseLockNAbortUpdate method.

The C syntax for the somrLock method is presented below:

_somrlock(repObject,    //pointer to an object of SOMRReplicbl
                env);   //pointer to Environment structure

This method is similar to the somrLockNlogOp method except it does not log the method information.

The C syntax for somrReleaseNPropagateUpdate follows:

_somrReleaseNPropagateUpdate(repObject,   //pointer to an object of SOMRReplicbl
                             env,         //pointer to Environment structure
                             className,   //name of class
                             but,         //buffer for update Information
                             buflen,      //size of buffer
                             objIntld);   //reserved for future use

The parameter repObject is a pointer to a replicated object. The parameter className is the class name of the replicated object. The parameter buf contains the information that is to be propagated. The parameter bufLen contains the size of the buffer. The parameter objlntld is reserved for future use and should be set to 0.

The C syntax for somrReleaseLockNAbortUpdate follows:

_somrReleaselockNAbortUpdate(repObject, // pointer to an object of SOMRReplicbl
                             env):      // pointer to Environment structure

Replica State

A replicated object must get and set the complete state of the object. The SOMRLinearizable class provides two methods, somrGetState and somrSetState. These must be overridden by a replicated class. The somrGetState method encodes the internal state of the object into a byte string. The somrSetState method converts the byte string into its internal state. The Replication Framework calls these methods on the replicated object to synchronize new replicas with the existing ones. The somrGetState method is called on existing replicas and the somrSetState method is called on the new replicas.

The C syntax for the somrGetState method is presented below:

_somrGetState(repObject,  //pointer to an object of SOMRLinearizable
              env,        //pointer to Environment structure
              but);       //buffer for outgoing byte string

The parameter repObject is a pointer to a replicated object. The parameter buf is the buffer that stores the internal state of the object. The caller must allocate the storage for the string, and the first four bytes must contain the length of the string.

The C syntax for the somrSetState method is shown below:

_somrSetState(repObject,   //pointer to an object of SOMRLinearizable
              env,         //pointer to Environment structure
              buf );       //buffer for incoming byte string

The parameter repObject is a pointer to a replicated object. The parameter buf is the buffer that contains the byte string for the internal state of the object. The first four bytes of the string contain the length of the string.

Replica Update

If value logging is used, the replicated object must override the method somrApplyUpdates. The somrApplyUpdates method receives and interprets messages propagated by other replicas. The C syntax for the somrApplyUpdates method follows:

_somrApplyUpdates(repObject,   //pointer to an object of SOMRRephcbl
                  env,         //pointer to Environment structure
                  buffer,      //buffer that contains update info
                  buflen,      //size of buffer
                  objlntld);   //reserved for future use

The parameter repObject is a pointer to a replicated object. The parameter buffer contains the update information and is in the same format as the one passed by the somrReleaseNPropagateUpdate method. The parameter buflen contains the size of the buffer. The parameter objlntld is reserved for future use, and should be set to 0.

Handle Directives

A replicated object must be able to receive and respond to data replication directives. A directive is a message sent from the Replication Framework to a replica, to indicate that some conditions have arisen asynchronously. The SOMRReplicbl class provides the method somrDoDirective, which should be overridden by a replicated subclass, to interpret the directives that are sent to the replica.

The C syntax for the somrDoDirective method is presented below:

_somrDoDirective(repObject,  //pointer to an object of SOMRReplicbl
                 env,        //pointer to Environment structure
                 directive);  //directive string

The parameter repObject is a pointer to a replicated object. The parameter directive is the string representing the directive. The current defined directives and their meanings are given in Table 7.1.

Table 7.1 RSOM directives
Directives Description
BECOME_STAND_ALONE The replica lost its connection to other replicas, and RSOM has given up trying to reconnect to the other replicas.
CONNECTION_LOST The replica has lost its connection to the other replicas, and RSOM is trying to reconnect.
CONNECTION_REESTABLISHED The connection between the replica and other replicas has been reestablished.
LOST_RECOVERABILITY RSOM cannot update the .scf (a) file to reflect the current state of the Framework, and recovery may be impacted.

a. The sci files are used by RSOM to establish communication CYT1009 the replicas. It is an internal implementation detail that is exposed to the user. One should not make use of those files.

Client Program

To use replicated objects, a client program must initialize the Replication Framework environment and set up the main loop for processing events. Because every operating system provides different mechanisms for processing events, the manner in which the event loop is set up will be different. SOM provides an Event Management Framework to organize events into groups, and to process them in a single event-processing loop.

RSOM Initialization

Every RSOM client must include the file <somr.xh> (or <somr.h> if using C bindings). This file includes the constants, global variables, and run-time interfaces that are used by RSOM. It must also include the file <eman.xh> (or <eman.h> if using C bindings). This file includes the run-time interfaces used by the Event Management Framework.

The client must also initialize the RSOM Framework by creating an instance of the SOMR. class. The SOMR class creates and initializes several manager objects required by RSOM. The client must also initialize the Event Management Framework by creating an Event Manager object. The Event Manager object handles the registration and processing of input events.

A typical implementation of RSOM initialization follows:

#include <somr.xh>
#include <eman.xh>
main()
{
   Environment *ev;
   SOMR*repEnv;
   SOMEEMan*emgr;

   ev = SOM_CreatelocalEnvironment();   //create and initialize an Environment structure
   repEnv =new SOMA();                  //create and initialize RSOM
   emgr = new SOMEEMan();               //create and initialize Event Management
   ...
}

Setup Event Processing Loop

The Event Manager class, SOMEEMan, provides the methods someProcessEvent and someProcessEvents for processing events. The someProcessEvent method processes one event and then returns. The someProcessEvents method loops forever waiting for events and dispatches them. Typically, a main program registers an interest in an event type and specifies a callback to be invoked, when the event occurs. The main program then calls someProcessEvents to wait on the registered events.

Note that in a single-threaded environment, such as AIX, once control is given to some Process Events, there is no way to respond to other input such as keyboard input. Therefore, one must register interest in "stdin" and provide a callback function to handle keyboard input before calling someProcessEvents. For multi-threaded environment such as OS/2, one can spawn a thread to execute someProcessEvents and another to handle keyboard input. This is demonstrated in our application.

Building and Registering the Classes

Building an RSOM application is very much like a regular SOM application. You compile your classes and your client program. 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 RSOM classes into the Interface Repository. The Interface Repository is described in more detail in Chapter 8, Working with the Interface Repository.

TCP/IP

The Replication Framework requires TCP/IP Version 1.2.1 with CSD UN34109 or later versions, to be installed and running. You can use the command file \TCPIP\BIN\TCPSTART.CMD to start TCP/IP execution. The Replication Framework uses sockets for inter-process communications. It also uses the Event Management Framework, which also uses sockets. The SOMobjects Developer Toolkit provides a Sockets class abstraction. TCPIPSockets is one of the derived class of Sockets and uses the TCP/IP implementation.

Execution

Make sure the environment variable SOMIR points to the Interface Repository that contains the replicated classes. The environment variable SOMSOCKETS must be set to the name of the socket implementation class. The default is TCPIPSockets. The RSOM application can then be started.

A Replicated Calendar

In this section, we will rewrite our calendar application using the RSOM Framework. We notice in our previous shared calendar that an update by one client is not automatically reflected in the other client's view. Say "Chris" calendar is accessed by two users and both are showing the work items for Monday. If the first user adds a calendar item to Monday, the second user will not see it until refreshing the Monday Hst. This is illustrated in Figure 7.1.

When user one adds a new work item (item3) to the shared calendar, user two will not see the new item. This is because even though the calendar data is shared by the two client processes, there is no mechanism in place to notify the other process when a change occurs in the shared data.

The Replication Framework can be used as a notification mechanism to solve the above problem. Using the Replication Framework, when an update occurs in a replica, the update is automatically propagated to the other replicas in the system. In other words, al] the replicas of an object get notified by the Framework when a change occurs in any one of the replicas. On receiving the notification, a replica can refresh its view to show the current status.

In the following, we show the modifications that are needed to implement such a system. The user interface for the PLANNER program remains the same. However, you will notice when you make an update to the calendar, all the other views of that calendar will automatically be refreshed.

High-Level Design

Recall that the Day class is used to keep track of the list of work items for a particular day. We make the Day class replicable. When one client updates a Day object, say the Monday object, the update will be propagated to all the replicas of the Monday object in the system. The classes WorkItem and CalendarDir that make up our calendar DLL do not change. They are identical to the Distributed Calendar implementation in Section 5.10 on page 108.

SOM-DSOM-fig-7 1.png

Figure 7.1 Multiple users working with the same calendar

In order for our client views of the calendar to show the updated list of work items for a particular day, a protocol must be in place so that whenever a Day object changes, a message is sent to each client views to inform them of the change. This is because the Day object, which contains only state information, has no idea of the possible views that are interested in its state.

We provide a simple registration protocol in our implementation. Any client that is interested in a change in a Day object will register such a desire with the Day class. The registration contains information as to what method to invoke when a change occurs. When a WorkItem is added to or removed from a Day object, it will invoke the registered method to notify the corresponding client.

A new SOM class DayView is added to provide the view encapsulation. The Day View class provides the bridge from the data object to the multiple views on the data object. Figure 7.2 shows the association between the view and the data.

SOM-DSOM-fig-7 2.png

Figure 7.2 Associating View with Data

When an update is made to a Day replica, the RSOM Framework will multicast the update to all the other replicas. Each replica can notify each view that has registered an interest in its state change. When the view receives the notification, it can update its display. This is summarized in Figure 7.3.

SOM-DSOM-fig-7 3.png

Figure 7.3 RSOM multicast update on replica triggers refresh of display

Implementation Details

This section provides the details on the design and implementation of each component in our replicated calendar.

The WorkItem Class

The IDL for the WorkItem class is given in Figure 7.4. The WorkItem class encapsulates work items for a day.

#ifndef workitem_idl
#define workitem_idl

#include <somobj.idl>

interface WorkItem : SOMObject
{
  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;

    dllname = "calendar.dll";
  };
  #endif
};
#endif

Figure 7.4 TheWorkItem IDL

The C++ implementation for the WorkItem class is the same as the one in the Distributed Calendar. Refer to Section 5.10.4 on page 110 for the details.

The Day Class

The IDL for the Day class is presented in Figure 7.5. This class is derived from SOMRReplicbl so that its objects are replicable.

#ifndef day_idl
#define day_idl

#include <replicbl.idl>

interface WorkItem;

interface Day : SOMRReplicbl
{
  const unsigned long MAXITEM = 50;
  attribute long date; 
  attribute sequence<WorkItem,MAXITEM> workList;

  short book2(in string start, in string end, in string desc);
  short removeItem(in string start, in string end, in string desc);

  void  initReplica(in string replicaName);
 
  void registerView(in SOMObject anyObj, in string methodName);
  void notifyView(in long eventId);
  SOMObject getViewObject();

  #ifdef __SOMIDL__
  implementation
  {
    releaseorder : _get_date, _set_date,
                   _get_workList, _set_workList,
                   book2, removeItem,
                   initReplica, registerView, notifyView,
                   getViewObject;

    string            methodName;
    SOMObject         objectPtr;

    somInit:          override;
    somUninit:        override;
    somrGetState:     override;
    somrSetState:     override;
    somrDoDirective:  override;

    dllname = "calendar.dll";
  };
  #endif
};
#endif

Figure 7.5 Day IDL

  • The book2 method acquires a lock on the current replica. It creates a new WorkItem object, adds it to the sequence workList, and propagates the update to the other replicas. It then invokes the notify View method to notify any view object that has registered an interest in the state change of the object.

Observe that the book2 method signature is different from the previous book method. The book method takes a WorkItem object as input parameter, while the book2 method takes the startTime, endTime, and task values as input parameters and then constructs the WorkItem object. This is because we use operation logging to propagate changes. Operation logging executes this method at every existing replica. Had we passed a WorkItem object, the WorkItem object would not exist in the different address spaces. Therefore, we need to pass explicit values, and then create the WorkItem object in each address space.

  • The removeltem method acquires a lock on the current replica and removes a WorkItem object from the sequence workList by searching the sequence for a WorkItem object that has the same startTime, endTime, and task values as the input parameters. If one is found, it will be removed from the sequence. The update is propagated to the other replicas and the notify View method is called to notify any view object that has registered an interest in the state change of the object.
  • The initReplica method sets the replica name and initializes the replica to use operation logging.
  • The register View method registers a view object that is interested in the state change of this replica. The registration information includes the name of the class and the name of the method to invoke when a change in replica state occurs.
  • The notify View method uses dispatch-function resolution to invoke the registered method. The notify View method is called whenever there is a change in the object state or whenever a directive is received.
  • The getViewObject method returns the view object that is associated with this replica.
  • The somlnit method is overridden to initialize the sequence workList. It allocates storage for the sequence and sets the length and maximum field of the sequence.
  • The somUninit method is overridden to free the storage that is allocated for the sequence workList when the object is destroyed.
  • The somrGetState method is overridden to encode the internal state of the object into a byte string. The internal state of the object consists of all the WorkItem objects that are associated with this Day object. Therefore each WorkItem object has to be flattened and appended to the byte string. The flattened information contains the length and the value of each field in the WorkItem object.
  • The somrSetState method is overridden to decode the byte string into the object internal state. Using the encoded length information, each field can be read in and a new WorkItem object can be re-created and added to the workList sequence.

Note that the current release of RSOM does not support composite objects in the sense that there is no lock propagation. For example, a lock on the Day object does not lock the WorkItem objects in the workList sequence. Therefore, one must implement code similar to that presented here in order to save and restore the state of the WorkItem objects. This presents a difficulty when using RSOM. The support for composite objects will hopefully be added in future releases.

  • The somrDoDirective method is overridden to handle the data replication directives. It calls notify View to notify the interested view on the directive that this replica received.

The C++ code that implements the methods for the Day class is given in Figure 7.6. The somrGetState method encodes each WorkItem object into a byte string using the following sequence: startTime length, startTime value, endTime length, endTime value, task length, task value. It also stores the total number of WorkItem objects in the byte string. The somrGetState method is called by the Replication Framework on an existing replica whenever a new replica enters the system. The Framework then calls the somrSetState method on the new replica to synchronize the new replica data with the existing ones. Using the selfdescribing byte string, the somrSetState method reconstructs each WorkItem object and adds it to the new replica sequence.

/*
 *  This file was generated by the SOM Compiler.
 *  Generated using:
 *     SOM incremental update: 2.7
 */

#define Day_Class_Source
#include <day.xih>
#include <workitem.xh>
#include <stdio.h>
#include <eventid.h>

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

    somSelf->somrLockNlogOp(ev, "Day", "book2", ev,
                            start, end, desc);
 
    entry = new WorkItem;
    entry->mkEntry(ev, start, end, desc);

    if (sequenceLength(somThis->workList) < sequenceMaximum(somThis->workList))
    {
       sequenceElement(somThis->workList,
                       sequenceLength(somThis->workList)) = entry;
       sequenceLength(somThis->workList)++;
       rc = 0L;
    }
    else
       rc = -1L;

    somSelf->somrReleaseNPropagateOperation(ev);

    //*******************************************************
    // Notify the different views so that they can refresh 
    // their display
    //*******************************************************
    somSelf->notifyView(ev, WM_REFRESH);
    return rc;
}

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

    rc = -1;
    somSelf->somrLockNlogOp(ev, "Day", "removeItem", ev,
                            start, end, desc);
 
    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);
        }
        rc = 0;
      }
    }

    somSelf->somrReleaseNPropagateOperation(ev);

    //*****************************************************
    // Notify the different views so that they can refresh 
    // their display
    //*****************************************************
    somSelf->notifyView(ev, WM_REFRESH);
    return rc;
}

SOM_Scope void  SOMLINK initReplica(Day *somSelf,  Environment *ev, 
                                    string replicaName)
{
    DayData *somThis = DayGetData(somSelf);
    DayMethodDebug("Day","initReplica");

    somSelf->somrSetObjName(ev, replicaName);
    somSelf->somrRepInit(ev, 'o', 'w');
}

SOM_Scope void  SOMLINK registerView(Day *somSelf,  Environment *ev, 
                                     SOMObject* anyObj, string methodName)
{
    DayData *somThis = DayGetData(somSelf);
    DayMethodDebug("Day","registerView");

    somThis->objectPtr = anyObj;
    somThis->methodName = methodName;
}

SOM_Scope void  SOMLINK notifyView(Day *somSelf,  Environment *ev, 
                                   long eventId)
{
    DayData *somThis = DayGetData(somSelf);
    DayMethodDebug("Day","notifyView");

    if (somThis->objectPtr) 
    {
      (somThis->objectPtr)->somDispatch((somToken*)0,
                         somIdFromString(somThis->methodName),
                         somThis->objectPtr,
                         ev,
                         eventId);
    }
}

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

    return (somThis->objectPtr);
}

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

    Day_parent_SOMRReplicbl_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_SOMRReplicbl_somUninit(somSelf);
}

SOM_Scope void  SOMLINK somrGetState(Day *somSelf,  Environment *ev, 
                                     string* buf)
{
    long bufsize;
    long seqsize;
    short i, len;
    long infoLen;
    char *start;
    WorkItem *item;
    DayData *somThis = DayGetData(somSelf);
    DayMethodDebug("Day","somrGetState");

    bufsize = sizeof(long) + sizeof(unsigned long);
 
    for (i=0; i < sequenceLength(somThis->workList); i++ )
    {
       item = sequenceElement(somThis->workList,i);
       bufsize += strlen(item->_get_startTime(ev)) + 1 + sizeof(long) +
                  strlen(item->_get_endTime(ev)) + 1 + sizeof(long) +
                  strlen(item->_get_task(ev)) + 1 + sizeof(long);
    }

    *buf = (char *) SOMMalloc(bufsize);

    //**************************************************
    // Store total length
    //**************************************************
    memcpy(*buf, &bufsize, sizeof(long));
    len = sizeof(long);

    //**************************************************
    // Store sequence Length
    //**************************************************
    memcpy((*buf+len),
           &sequenceLength(somThis->workList),
           sizeof(unsigned long));
    len += sizeof(unsigned long);

    //************************************************** 
    // Store sequence Elements
    //************************************************** 
    for (i=0; i < sequenceLength(somThis->workList); i++ )
    {
      item = sequenceElement(somThis->workList,i);

      //*********************************************** 
      // Store length of startTime and startTime
      //***********************************************
      infoLen = strlen(item->_get_startTime(ev)) + 1;

      memcpy((*buf+len), &infoLen, sizeof(long));
      len += sizeof(long);

      strcpy( (char*)(*buf+len), item->_get_startTime(ev));
      len += infoLen;

      //***********************************************
      // Store length of endTime and endTime
      //*********************************************** 
      infoLen = strlen(item->_get_endTime(ev)) + 1;
      memcpy((*buf+len), &infoLen, sizeof(long));
      len += sizeof(long);

      strcpy((char*)(*buf+len), item->_get_endTime(ev));
      len += infoLen;
 
      //***********************************************
      // Store length of task and task
      //*********************************************** 
      infoLen = strlen(item->_get_task(ev)) + 1;
      memcpy((*buf+len), &infoLen, sizeof(long));
      len += sizeof(long);

      strcpy((char*)(*buf+len), item->_get_task(ev));
      len += infoLen;
    }
}

SOM_Scope void  SOMLINK somrSetState(Day *somSelf,  Environment *ev, 
                                     string buf)
{
    long totlen, seqlen;
    short len;
    long infoLen;
    WorkItem *entry;
    short i;
    char *start, *end, *task;
    DayData *somThis = DayGetData(somSelf);
    DayMethodDebug("Day","somrSetState");

    totlen = *(long *) buf;
    len = sizeof(long);

    seqlen = *( (unsigned long *)(buf + len) );
    len += sizeof(unsigned long);

    for (i=0; i < seqlen; i++) 
    {
       //***********************************************
       // Get startTime
       //***********************************************
       infoLen = *( (long *)(buf+len) );
       len += sizeof(long);

       start = (char *)SOMMalloc(infoLen);
       memcpy(start, (buf + len), infoLen);
       len += infoLen;

       //***********************************************
       // Get endTime
       //***********************************************
       infoLen = *( (long *)(buf+len) );
       len += sizeof(long);
 
       end = (char *)SOMMalloc(infoLen);
       memcpy(end, (buf + len), infoLen);
       len += infoLen;
 
       //***********************************************
       // Get task
       //***********************************************
       infoLen = *( (long *)(buf+len) );
       len += sizeof(long);
 
       task = (char *)SOMMalloc(infoLen);
       memcpy(task, (buf + len), infoLen);
       len += infoLen;
 
       //********************************************** 
       // Create WorkItem
       //**********************************************
       entry = new WorkItem;
       entry->mkEntry(ev, start, end, task);
 
       sequenceElement(somThis->workList,i) = entry;
       SOMFree(start);
       SOMFree(end);
       SOMFree(task);
    } 

    sequenceLength(somThis->workList) = seqlen;
}

SOM_Scope void  SOMLINK somrDoDirective(Day *somSelf,  Environment *ev, 
                                        string str)
{
   DayData *somThis = DayGetData(somSelf);
   DayMethodDebug("Day","somrDoDirective");

   if (strcmp(str, "BECOME_STAND_ALONE") == 0)
   {
       somSelf->notifyView(ev, WM_STAND_ALONE);

   } 
   else
   {
      if (strcmp(str, "CONNECTION_LOST") == 0)
      {
          somSelf->notifyView(ev, WM_CONNECTION_LOST);
      }
      else
      {
         if (strcmp(str, "CONNECTION_REESTABLISHED") == 0)
         {
             somSelf->notifyView(ev, WM_CONNECTION_REESTABLISH);
         }
         else
         {
            if (strcmp(str, "LOST_RECOVERABLILTY") == 0)
            {
                somSelf->notifyView(ev, WM_LOST_RECOVER);
            }
         }
      }
   }

   Day_parent_SOMRReplicbl_somrDoDirective(somSelf, ev, str);
}

Figure 7.6 The Day class implementation

The CalendarDir Class

The IDL for the Calendar Dir class is shown in Figure 7.7. It is used as a directory to keep track of the list of days of the week.

#ifndef caldir_idl
#define caldir_idl

#include <somobj.idl>

interface Day;
interface CalendarDir : SOMObject
{
  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;
    dllname = "calendar.dll";

  };
  #endif
};
#endif

Figure 7.7 The CalendarDir IDL

The C++ implementation for ihe CalendarDir class is the same as the one in the Distributed Calendar. Refer to Section 5.10.4 on page 110 for the details.

The DayView Class

The IDL for the DayView class is given in Figure 7.8. The Day View class is used to encapsulate the view for the Day class. Recall that our view of the calendar objects is a GUI client, DayPlannerWindow, written using C++ classes. We would like to store a pointer to the Day Plan nerWindow object so that we can notify it when there is a state change in the Day object. However, because IDL is language neutral, it does not allow the specification of C++ objects as parameter types. To overcome the problem, we declare an attribute owner of type void *, so we can store the pointer to the C++ object. We then cast it to the appropriate C++ class in the code.

#ifndef dayview_idl
#define dayview_idl

#include <somobj.idl>

interface DayView : SOMObject
{
  attribute void *owner;
  void notifyDayPlanner(in long eventId);
 
  #ifdef __SOMIDL__
  implementation
  {
    releaseorder: _get_owner, _set_owner,
                  notifyDayPlanner;
  };
  #endif
};
#endif

Figure 7.8 The DayView IDL

The DayView class introduces one new method, notifyDayPlanner. This method sends a message to the DayPlannerWindow object, so it can refresh its display.

The C++ implementation for the Day View class is shown in Figure 7.9.

/*
 *  This file was generated by the SOM Compiler and Emitter Framework.
 *  Generated using: 
 *      SOM Emitter emitxtm: 2.7
 */

#define DayView_Class_Source
#include <dayview.xih>
#include <iwindow.hpp>

SOM_Scope void  SOMLINK notifyDayPlanner(DayView *somSelf,  Environment *ev, 
                                         long eventId)
{
    DayViewData *somThis = DayViewGetData(somSelf);
    IWindow *winOwner;
    DayViewMethodDebug("DayView","notifyDayPlanner");

    winOwner = (IWindow *) somThis->owner;
    winOwner->postEvent(eventId);
}

Figure 7.9 The DayView class implementation

The GUI Client

The PLANNER program is modified to work with the Replication Framework. The listing for planwin.hpp appears in Figure 7.10.

#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 <ithread.hpp>

#include <somr.xh>
#include <eman.xh>

#include "week.h"
#include "workitem.xh"
#include "day.xh"
#include "caldir.xh"
 
#include "planhdr.hpp"

class DayPlannerWindow : public IFrameWindow,
                         public ICommandHandler,
                         public ISelectHandler,
                         public PlannerHandler
{
 public:
   DayPlannerWindow (unsigned long windowId);
   ~DayPlannerWindow();
   void checkEvents();

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

 private:
   setupClient();
   setupData();
   showItems(short day);
   book(char *start, char *end, char *desc);
   remove(char *start, char *end, char *desc);
   refreshListBox();
   registerView(Day *day);

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

   IMultiCellCanvas *mc;
   IMenuBar         *menuBar;
   short            menuFlag;

   IListBox         *listBox;
   IListBox::Cursor *cursor;

   IThread     thread;
   SOMEEMan    *emgr;
   SOMR        *repobj;
   Environment *ev;
   CalendarDir *curDirEntry;
   Day         *curDay;
}; 

class eventMgrThread : public IThreadFn
{
   public:
     eventMgrThread(DayPlannerWindow &obj)
        : planObj(obj)
        {;}
 
     void run() { planObj.checkEvents(); }

   private:
     DayPlannerWindow &planObj;
};

#endif

Figure 7.10 The DayPlannerWindow header

In addition to inheriting from the IFrame Window, ICommandHandler, and ISelectHandler classes, the class DayPlannerWindow also inherits from the PlannerHandler class. The PlannerHandler class processes events that are specific to the DayPlannerWindow. A summary of each member function in DayPlannerWindow follows.

  • The planner Message function provides the handling for a refresh event. When a WM_REFRESH event is received, it calls the refreshListBox function to refresh the list of work items for the current day. When a directive event is received, it pops up a message box to inform the user.
  • The setupClient function creates a multi-cell canvas control as the client window. It also creates the spin-button controls, the static text controls, the entry field control, and the list box control, and places them on the multi-cell canvas.
  • The setupData function initializes the RSOM environment and the Event Manager. It creates a new CalendarDir object and seven Day objects. Each Day object is given a name. In addition, it calls register View to register a view object with each Day object. It then starts a secondary thread for the SOM Event Manager.
  • The showltems function displays the list of activities for a particular day.
  • The book function creates a new Workltem object and adds it to the activitiy list for the day.
  • The remove function removes the selected Workltem for the day.
  • The refreshListBox function redisplays the work items for the current day.
  • The registerView function creates a new DayView object and registers it with the specified Day object.

The listing for the source file planwin.cpp is given in Figure 7.11.

#define INCL_DOS
#define INCL_ERRORS
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
 
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
 
#include <eventmsk.h>

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

#include "planwin.hpp"
#include "dayview.xh"

HMTX  keyboard_sem;

void main(int argc, char *argv[], char *envp[])
{
  DayPlannerWindow mainWindow(WND_MAIN);
  IApplication::current().run(); 
}

DayPlannerWindow :: DayPlannerWindow(unsigned long windowId)
  : IFrameWindow (IFrameWindow::defaultStyle(), windowId)
{
   IString title("Weekly Calendar");

   ICommandHandler::handleEventsFor(this);

   menuBar = new IMenuBar(WND_MAIN,this);
   menuBar->setAutoDeleteObject();
   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();

   // Enable PlannerHandler to handle user-defined events
   PlannerHandler::handleEventsFor(this);
   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;
  char  *dayName[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  ULONG semattr;
  long  rc;

  semattr = DC_SEM_SHARED;
  rc = DosCreateMutexSem((PSZ) NULL, 
                         (PHMTX)&keyboard_sem, semattr, 0);
  if (rc != 0)
  {
    FILE *fp;
    fp = fopen("debug.dat", "a");
    fprintf(fp, "Error Allocating shared semaphore: rc = %d \n",rc);
    fclose(fp);
    exit(1);
  }

  ev = SOM_CreateLocalEnvironment();
  repobj = new SOMR();
  emgr = new SOMEEMan();

  curDirEntry = new CalendarDir();
  for (i=0; i < 7 ; i++ )
  {
     day = new Day();
     day->_set_date(ev,i);
     day->initReplica(ev, dayName[i]);      // set replica name
     curDirEntry->addDay(ev,i,day);
     registerView(day);                     // register view
  }

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

  //***************************************************
  // Start a new thread for SOM event manager
  //***************************************************
  eventMgrThread *emgrFn = new eventMgrThread( *this );

  //*************************************************** 
  // Dispatch thread to run function
  //***************************************************
  thread.start( emgrFn );
}

//************************************************* 
// Register a DayView object with the Day object
//*************************************************
DayPlannerWindow :: registerView(Day *day)
{
  DayView *dv;

  dv = new DayView;
  dv->_set_owner(ev, (void *)this);

  day->registerView(ev, dv, "notifyDayPlanner");
}

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

  for (i=0; i < 7; i++ ) 
  {
     curDay = curDirEntry->getDay(ev,i);
     alist = curDay->_get_workList(ev);

     //*********************************************
     // Destroy each WorkItem from the Day object
     //*********************************************
     for (j=0; j < sequenceLength(alist) ; j++)
     {
        delete sequenceElement(alist,j);
     }

     //********************************
     // Destroy DayView object
     //********************************
     delete (curDay->getViewObject(ev));

     delete curDay;
  }

  delete emgr;
  delete repobj;


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

        remove(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;
}

//****************************************************
// Handler for user-defined messages
//****************************************************
Boolean DayPlannerWindow :: plannerMessage(IEvent& evt)
{
  IMessageBox msgbox(this);

  if (evt.eventId() == WM_REFRESH)
  {
        refreshListBox();
        return true;
  }
  if (evt.eventId() == WM_STAND_ALONE)
  {
        msgbox.show("stand alone", IMessageBox::okButton);
        return true;
  }
  if (evt.eventId() == WM_CONNECTION_LOST)
  {
        msgbox.show("Connection lost", IMessageBox::okButton);
        return true;
  }
  if (evt.eventId() == WM_CONNECTION_REESTABLISH)
  {
      msgbox.show("Connection reestablished", IMessageBox::okButton);
      return true;
  }
  if (evt.eventId() == WM_LOST_RECOVER)
  {
      msgbox.show("Lost recoverability", IMessageBox::okButton);
      return true;
  }

  return false;
}

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

DayPlannerWindow :: showItems(short day)
{
  menuBar->uncheckItem(menuFlag);   // uncheck previous day
  menuBar->checkItem(MI_BASE+day);  // check selected day
  menuFlag = MI_BASE + day;

  curDay = curDirEntry->getDay(ev,day);
  refreshListBox();
}

DayPlannerWindow :: refreshListBox()
{
  _IDL_SEQUENCE_WorkItem alist;
  short                  i;
  IString                str;

  alist = curDay->_get_workList(ev); 

  listBox->removeAll();

  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)
{
   while  (DosRequestMutexSem(keyboard_sem, SEM_INDEFINITE_WAIT) != 0);
 
   curDay->book2(ev, start, end, task);

   DosReleaseMutexSem(keyboard_sem);
}

DayPlannerWindow :: remove(char *start, char * end, char *task)
{
   while  (DosRequestMutexSem(keyboard_sem, SEM_INDEFINITE_WAIT) != 0);
 
   curDay->removeItem(ev, start, end, task);
 
   DosReleaseMutexSem(keyboard_sem);
}

//*********************************************************
// SOM Event Manager loop. It runs on its own thread
//*********************************************************
void DayPlannerWindow :: checkEvents()
{
  short         rc;
  IMessageBox msgbox(this);

  while (1)
  {
     // Wait for 500 milliseconds
     DosSleep(500);
    
     // Wait on and take the mutual exclusion semaphore
     while  (DosRequestMutexSem(keyboard_sem, SEM_INDEFINITE_WAIT) != 0);

     // When control is obtained, process events
     emgr->someProcessEvent(ev, EMProcessTimerEvent | EMProcessSinkEvent);
 
     // Release the semaphore
     DosReleaseMutexSem(keyboard_sem);
  }
}

Figure 7.11 The DayPlannerWindow implementation

The PlannerHandler Class

The PlannerHandler class provides a handler for the message that is specific to the DayPlannerWindow. It overrides the dispatchHandlerEvent member function and provides a virtual callback function to process the message. The listing for planhdr.hpp is shown in Figure 7.12. For more information on handlers and events, please refer to the Redhook for the IBM User Interface Class Library.

#ifndef PLANHDR_HPP
#define PLANHDR_HPP

#include <ihandler.hpp>

#include "eventid.h"

class PlannerHandler : public IHandler
{
   public:
     Boolean
        dispatchHandlerEvent( IEvent& evt);

   protected:
     virtual Boolean plannerMessage( IEvent& evt);
};
#endif

Figure 7.12 The PlannerHandler header file

The listing for planhdr.cpp is shown in Figure 7.13 (p. 212). It filters the events so it will only call the plannerMessage function, when it is one of the expected events. The default implementation returns FALSE so that an eve nt can be passed on to other handlers. The plannerMessage is overridden in the DayPlanner Window to provide the specific event handling required.

#include "planhdr.hpp"
#include <stdio.h>

Boolean PlannerHandler :: dispatchHandlerEvent(IEvent &evt)
{
   if (evt.eventId() == WM_REFRESH                ||
       evt.eventId() == WM_STAND_ALONE            ||
       evt.eventId() == WM_CONNECTION_LOST        ||
       evt.eventId() == WM_CONNECTION_REESTABLISH ||
       evt.eventId() == WM_LOST_RECOVER)
   {
      //************************************************
      // events that are of interest to DayPlannerWindow
      //************************************************
      return plannerMessage(evt);
   } 
   return false;
}

Boolean PlannerHandler :: plannerMessage(IEvent& evt)
{
   // Provides default return
   return false;
}

Figure 7.13 The PlannerHandler implementation

The Makefile

The source files for building the calendar DLL are workitem.cpp, day.cpp, caldir.cpp, initfunc.cpp, and calendar.def.

The source file dayview.cpp contains the Day View class. Theso urce file planhdr.cpp contains the PlannerHandler class. The planwin.hpp, planwin.cpp, dayview.hpp, dayuiew.cpp, planhdr.hpp, planhdr.cpp, week.re, and week.h source files are needed to build the GUI client.

The makefile for building the Replicated Calendar application is given in Figure 7.14 (p. 213). It includes dependencies to build the Interface Repository. The classes that make up the calendar DLL are compiled into the Interface Repository.

.SUFFIXES:
.SUFFIXES: .idl .xih .xh .cpp .obj .def 
#
# Need /Ge- to build DLL
#
OBJS  = caldir.obj workitem.obj day.obj initfunc.obj
OBJS2 = dayview.obj planhdr.obj

UILIB = dde4muii.lib dde4cci.lib dde4mbsi.lib os2386.lib

all: calendar.dll planner.exe som.ir

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

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

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

dayview.xih: dayview.idl
dayview.xh:  dayview.idl
dayview.cpp: dayview.xih

planview.obj: planview.xih planview.xh planview.cpp
planview.xih: planview.idl
planview.xh:  planview.idl
planview.cpp: planview.xih

initfunc.obj: initfunc.cpp

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

planhdr.obj: planhdr.cpp planhdr.hpp
        icc /c+ /Gd+ /Gm+ /Si+ -I. planhdr.cpp
dayview.obj: dayview.cpp dayview.xh dayview.xih
        icc /c+ /Gd+ /Gm+ /Si+ -I. dayview.cpp

week.res: week.rc week.h
          rc -r week.rc
#
# Put the IDL descriptions into the Interface Repository
#
som.ir: caldir.idl workitem.idl day.idl
        sc -sir -u workitem.idl
        sc -sir -u day.idl
        sc -sir -u caldir.idl

Figure 7.14 The replicated PLANNER Makefile

Building the Application

To build the application, invoke NMAKE using the makefile shown in Figure 7.14.

Running the Application

TCP/IP must be started before you can run the Replicated Calendar. The environment variable SOMIR must be set to point to the Interface Repository that contains the IDLs for the calendar classes. The environment variable SOMSOCKETS must also be set. For example, you can use the following settings:

 set SOMIR=%SOMBASE%\etc\som.ir;som .ir
 set SOMSOCKETS= TCPIPSockets

The file rsomenv.cmd is provided to set up the enviromental variables.

Start the PLANNER program by typing:

 >planner

Add some activities to the calendar. For example, a department meeting is scheduled for Monday, from 10:00 to 11:00. A tennis game is booked for Tuesday, from 17:00 to 18:00.

In a separate window, run rsomenv.cmd to set up the environment variables. Then type "planner to start a second PLANNER program. Notice how all the activities that were added in the first PLANNER program appear in the second PLANNER program. This is because the Replication Framework synchronizes the two replicas of the "Mon" object.. If you add or delete an activity from either calendar, it will be automatically updated on the other calendar. You do not need to refresh the list explidtly. The multi-cast capability of RSOM, and the view notification mechanism that we implemented, gave us this function.

Try starting more PLANNER programs and terminating some and observe the following effects.

  • When you start another PLANNER program, its data is synchronized with the rest.
  • When you terminate a PLANNER program, existing PLANNER programs continue to function.
  • When you terminate a PLANNER program, message boxes pop up with a "Connection Lost" message.

The "Connection Lost" message occurs when the master replica terminates or crashes. The RSOM Framework uses a master I shadow implementation model. One replica is designated as a master, and the other replicas are shadows. If a shadow crashes, it does not affect the group of replicas, and they continue to operate. However, if the master crashes, it takes some time before the shadows recognize this and elect a new master. The Framework sends the CONNECTION_LOST directive. You should not update the replica until a connection has been re-established. If a connection cannot be re-establis hed by the Framework, you should reconnect the application.

A calendar can manifest itself in many different forms. A weekly planner like the one we have imp]emented is one form. Another form might be a daily reminder that only displays the calendar events for each day and beeps five minutes before each scheduled event. As an exercise, you might want to imp1ement the following: Write a new GUI for a daily reminder and register its view with the replicated Day c1ass so that whenever a new event is added to the weekly planner, the daily reminder will be notified as well.

Summary

This chapter explored an alternate imp1ementation for sharing objects across different address spaces using the RSOM Framework. Although we have not made the replicated objects persistent, one could easily add this by inheriting from both SOMRReplicbl and SOMPPersistentObject.

The choice between DSOM and RSOM depends on a number of factors. While RSOM maintains a complete replica of an object in each process' address space, DSOM establishes a remote connection to the object which is maintained by a server process. For applications that involve multiple parties, and where the parties must feel like they are touching each other by receiving instantaneous feedback, RSOM might be a good candidate. For app1ications that require only the sharing of objects among multiple processes, with Jess emphasis on instantaneous feedback, DSOM might be sufficient.

Other factors that come into play include the availability and the limitations of each Framework. In the current release, RSOM does not support heterogeneous configuration. That is, one can only replicate objects within a single machine environment. The lack of support for composite objects in RSOM makes it difficult to support complex applications. Therefore, if any of these are required, DSOM could be your only choice.