Programming Guide


Storage

This section provides a roadmap for those who want to learn how to use the OpenDoc storage using the recipes. It is assumed that you have a basic knowledge of OpenDoc and its storage system (including container, document, draft, and storage unit).

Storage recipes contains the following documents that discuss the basic storage concepts and usage:

The following recipe is specific only to the bento container suite:

Cloning Mechanism

                           

Every OpenDoc draft contains a network of storage units. These storage units are connected to each other through persistent references. See the "Persistent References" recipe for more information. When an OpenDoc draft is opened, ODPersistentObjects and ODStorageUnit objects are instantiated using the storage units.

Copying an object is not as straight forward as the usual stream operations. For example, when one tries to copy a value, one needs to copy all the storage units that are being referred to by the value also. Otherwise, the data copied is not complete.

To help developers accomplish this task more easily, OpenDoc provides a cloning mechanism to enable deep-copy through a simple function. This function has the following characteristics:

  1. Clients have to go through source ODDraft to clone any object. ODDraft in turn calls the appropriate object's CloneInto method. Clients should never call the CloneInto method of another object directly.

  2. Cloning is a transacted operation. Therefore, clients have to start the cloning operation by calling BeginClone and commit the cloning operation by calling EndClone. If for any reason, cloning cannot be completed, the client should call AbortClone to abort the transaction.

  3. The cloning mechanism is biased toward running objects. Therefore, if there is a running object corresponding to a particular storage unit, the cloning mechanism will call the running object's CloneInto method instead of the storage unit's. This ensures that externalize does not need to be called before any data interchange.

  4. The cloning mechanism respects a scope and the scope can only be a frame object or a storage unit belonging to a frame.

  5. There are different kinds of cloning. They are mainly for data interchange purposes. See the "Clipboard Recipes" recipe for more information.

  6. The ids returned from Clone and WeakClone cannot be used between BeginClone and EndClone. Therefore, if you want to use one of those ids for getting the object or making a reference, you will have to wait till after EndClone.

  7. Some of ids returned may turn out to be invalid because the corresponding objects have not been strongly cloned. Refer to the appropriate recipes to determine what to do in specific situations.

Recipe for Clients

Clients need to use the following ODDraft function calls:

ODDraftKey BeginClone(in ODDraft destDraft,
                      in ODFrame destFrame,
                      in ODCloneKind kind);

void EndClone(in ODDraftKey key);

void AbortClone(in ODDraftKey key);

ODID Clone(in ODDraftKey key,
           in ODID fromObjectID,
           in ODID toObjectID,
           in ODID scope);

ODID WeakClone(in ODDraftKey key,
               in ODID objectID,
               in ODID toObjectID,
               in ODID scope);

BeginClone, EndClone and AbortClone are for setting up and terminating the cloning transaction. Clone and WeakClone are used to notify the draft that the specified object needs to be cloned.

  1. Given the following network of objects, if the client wants to clone A, the client needs to do the following:

    Figure 85. Network of Objects



    View figure.


    ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy);
    ODID newA = draft->Clone(ev, key, A->GetID(ev), 0, 0);
    draft->EndClone(ev, key);
    

    The result is that A, B, C, D are all copied.

  2. Given the same network of objects in step 1 if the client wants to clone C, the client needs to do the following:

    ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy);
    ODID newB = draft->WeakClone(ev, key, B->GetID(ev), 0, 0);
    ODID newD = draft->Clone(ev, key, D->GetID(ev), 0, 0);
    draft->EndClone(ev, key);
    

    The result is that C and D are copied. B is not copied because it is not strongly referenced by C or any objects transitively strongly referenced by C. The newB returned by WeakClone is an invalid ID. Therefore, when the client tries to get that object using newB, an error results. It is the responsibility of the client to handle this error condition.

    Also, the IDs can only be used to make storage unit references during cloning. The client should not try to get the persistent object or storage unit.

    The following diagram shows what the copied network of storage units or persistent objects looks like: Figure 86

    Figure 86. Copied Network of Storage Units/Persistent Objects



    View figure.

  3. If the network in step 2 looks like the following, the CloneResult will be different.

    In this case, the code fragment in step 2 copies C, D and B. Then, the returned newB is a valid ID referring to the new copy of B. The following diagram shows what the resulting network looks like:

Recipe for Parts

All persistent objects (including parts) have to override CloneInto in ODPersistentObject:

void CloneInto(in ODDraftKey key,
               in ODStorageUnit toSU,
               in ODFrame scope);

During the clone operation (that is, between BeginClone and EndClone), a persistent object's CloneInto method is called by the draft to do the actual data copying. The recipe is very similar to that for the clients except that the persistent object has to write out its intrinsic content in addition to cloning its referenced storage units/persistent objects.

The following is an example of what a part editor might do in response to CloneInto:

void MyPartCloneInto(MyPart* somSelf,
                     Environment* ev,
                     ODDraftKey key,
                     ODStorageUnit toSU,
                     OID scopeFrameID)
{
  // Since this method may be called multiple times during one cloning
  // transaction, the part should check to see whether it really needs
  // to write out any data.
  if (toSU->Exists(ev, kODPropContents, kMyContentKind, 0) == kODFalse)
  {
    // Write out intrinsic content
    toSU->AddProperty(ev, kODPropContents);
    toSU->AddValue(ev, kMyContentKind);
    toSU->SetValue(ev, myContentByteArray);

    // Write out referenced object
    ODStorageUnit* mySU= somSelf->GetStorageUnit(ev);
    ODDraft* myDraft = mySU->GetDraft(ev);
    ODID newReferencedObjectID =
    myDraft->Clone(ev, key, myReferencedObject, 0, scopeFrameID);

    // Write out referenced object id
    ODStorageUnitRef ref;
    toSU->GetStrongStorageUnitRef(ev, newReferencedObjectID, ref);
    toSU->SetOffset(ev, correctOffsetInMyContent);

    // Set up the byte array
    ODByteArray refByteArray;
    refByteArray._length = sizeof(ODStorageUnitRef);
    refByteArray._maximum = sizeof(ODStorageUnitRef);
    refByteArray._buffer = &ref;
    toSU->SetValue(ev, &refByteArray);
  }
}

Scoping for Cloning

   

If a part has two frames and the user only chooses to copy one frame, the part should be notified about it. Otherwise, the part may copy too much data or in certain cases the wrong data. Let's consider the following situation. Part A contains part B (which has two display frames). In turn, part B contains part C and part D.

If the user selects some intrinsic content with B1 (as shown in the thin box) and selects Copy, part A should do the following:

// Begin cloning transaction
ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy);

// Write out intrinsic content to storageUnit
  ...
// Get reference to frame B1 using frame B1 as the scope
ODID newB1 = draft->Clone(ev, key, B1->GetID(ev), 0, B1->GetID(ev));

// Write out the reference to storageUnit also.
  ...
// Finish cloning
draft->EndClone(ev, key);

When part B's CloneInto method is called, the scope parameter is the ID of frame B1. In this way, part B can choose to clone only the necessary data.

However, if the user selects both B1 and B2 (as shown below), Part A should do the following:

// Begin cloning transaction
ODDraftKey key = draft->BeginClone(ev, destDraft, kODNULL, kODCloneCopy);

// Write out intrinsic content to storageUnit
  ...
// Get reference to frame B1 using frame B1 as the scope
ODID newB1 = draft->Clone(ev, key, B1->GetID(ev), 0, B1->GetID(ev));

// Get reference to frame B2 using frame B2 as the scope
ODID newB2 = draft->Clone(ev, key, B2->GetID(ev), 0, B2->GetID(ev));

// Write out the reference to storageUnit also
  ...
// Finish cloning draft->EndClone(ev, key);

As a result, part B's CloneInto method is called twice (once with frame B1 as the scope and another time with frame B2 as the scope). Part B should be ready to handle this and write out the part content accordingly.

For more information on Data Interchange, see the "Data Interchange" recipes (especially the "Clipboard Recipes" recipe).

Destination Frame for Cloning

 

When the ODCloneKind argument to BeginClone is kODClonePaste or kODCloneDropMove, parts must supply as the ODFrame argument the frame that is performing the paste or receiving the drop. For other values of ODCloneKind, the frame may be kODNULL, as in the examples in this document. This parameter allows OpenDoc to determine if the destination is a valid location for content being moved; an invalid destination frame is one that would cause a display frame of a part to be embedded within another display frame of the same part. In this unusual situation, the subsequent call to EndClone raises an exception. When EndClone returns an error, parts must call AbortClone (which does not return errors).

Persistent Object Annotations

   

As discussed above, the cloning mechanism is biased toward runtime objects. Therefore, if there is anything in the storage unit that is not stored by the runtime persistent object itself, it is not going to be cloned. An example of this may be a utility like a spell checker. It may store a list of words in the part's storage unit.

To make sure that this information is not lost, the container suite copies any properties with the following prefix to the destination storage unit:

const ODPropertyName kODPropPreAnnotation=
      "+//ISO 9070/ANSI::113722::US::CI LABS::OpenDoc:Annotation:";

The persistent object being cloned does not even need to know that these annotations exist.

Reference Counting

   

This section discusses the following:

Why Use ODRefCntObject?

During an OpenDoc session, many objects are created. Because there is a very complex relationship among objects, it is difficult to determine when it is safe to delete an object.

Reference counting is a way to determine when these runtime objects can be deleted so that valuable memory space can be reclaimed.

What Is ODRefCntObject?

ODRefCntObject is an object with a reference count. A reference count must be 0 or a positive integer.

Here is the class definition of ODRefCntObject:

interface ODRefCntObject:ODObject
{
  void  InitRefCntObject();
  void  Acquire();
  void  Release();
  ODULong  GetRefCount();
};

What Are the ODRefCntObjects in OpenDoc?

These are the subclasses of ODRefCntObject class:

How Does It Work?

  1. Every ODRefCntObject constructed has a reference count of 1.

  2. Every ODRefCntObject is created or acquired from a factory object, an object that keeps track of it.
    ODRefCntObject Factory Object
    ODContainer ODStorageSystem
    ODDocument ODContainer
    ODDraft ODDocument
    ODExtension ODObject (or subclass)
    ODFrame ODDraft
    ODLink ODDraft
    ODLinkSource ODDraft
    ODPart ODDraft
    ODShape ODFrame
    ODStorageUnit ODDraft
    ODTransform ODFrame
    ODWindow ODWindowState

    For example, to get a ODFrame object, one has to call ODDraft's CreateFrame or ODDraft's AcquireFrame. The ODFrame object returned from ODDraft's CreateFrame has a reference count of 1 while the ODFrame object returned from ODDraft's AcquireFrame has a reference count of at least 1.

    Let's look at a very simple example:

    // frame1's RefCount is 1
    ODFrame* frame1 = draft->CreateFrame(...);
    
    // frame2 == frame1 and its RefCount is 2
    ODFrame* frame2 = draft->AcquireFrame(...);
    
    // frame2's RefCount is 1.
    // Since frame1 == frame2, frame1's RefCount is also 1.
    frame2->Release();
    
    // The user should not use frame2 after this point!
    frame1->Acquire(...);  // frame1's RefCount is 2
    frame1->Release(...);  // frame1's RefCount is 1
    frame1->Release(...);  // frame1's RefCount is 0
    
    // The user should not use frame1 after this point!
    frame1->Acquire(...); or   // ERROR!
    frame1->Release(...);      // ERROR!
    

  3. When the RefCount goes down to 0, it is the object's responsibility to tell the factory object about it.

    For example, ODFrame's Release() can be implemented like this:

    void ODFrameRelease(ODFrame* somSelf, Environment* ev)
    {
      parent_Release(somSelf, ev); // which calls ODRefCntObject's Release()
      if (somSelf->GetRefCount(ev) == 0)
      fDraft->ReleaseFrame(ev, somSelf); // Call the factory object
    }
    

    The factory object can choose to dispose of the object immediately or keep the object around until a purge is called upon itself.

    In the OpenDoc implementation, ODDraft uses the latter strategy. Therefore, ODDraft does not delete the object as soon as ODDraft's ReleaseXXX is called. Instead, it puts this released object in a collection so that it can be retrieved and reused again (that is, when ODDraft's AcquireXXX is called for that object). But, if ODDraft's Purge() is called, all the released objects in the collection will be deleted.

    The reason why the object is not deleted immediately is that the object may be reused in the near future. One good example is scrolling. Parts may choose to release objects that are scrolled out of view and re-get them when they come into view again. If we delete the objects as soon as ReleaseXXX is called, we will have to re-instantiate these objects again. Creating persistent objects is not a fast process.

  4. Every time a reference to a ODRefCntObject (that is, ODRefCntObject pointer) is cached in some data structure, ODRefCntObj->Acquire() should be called by the code that manipulates this data structure. When the code is finished with the ODRefCntObj reference in the data structure, it should call ODRefCntObj->Release().

    For example, since ODFrame is keeping a reference to the containing frame and the part, it has to increment the refCounts on both of these objects.

    void ODFrameInitFrame(ODFrame* somSelf,
                          Environment* ev,
                          ODStorageUnit* storageUnit,
                          ODFrame* containingFrame,
                          ODShape* frameShape,
                          ODPart* part,
                          ODTypeToken viewType,
                          ODTypeToken presentation,
                          ODULong frameGroup,
                          ODBoolean isRoot,
                          ODBoolean isOverlaid)
    {
      ...
      _fContainingFrame = containingFrame;
      if (_fContainingFrame != kODNULL)
          _fContainingFrame->Acquire(ev);
      _fPart = part;
    
      if (_fPart != kODNULL)
          _fPart->Acquire(ev);
      ...
    }
    

    In ODFrame's ReleaseAll(), the frame has to release the containing frame and the part. Otherwise, the containing frame and the part always have a RefCount greater than 0 and they can never be disposed of.

    void ODFrameReleaseAll(ODFrame* somSelf, Environment* ev)
    {
      if (_fContainingFrame != kODNULL)
          _fContainingFrame->Release(ev);
      if (_fPart != kODNULL)
          _fPart->Release(ev);
    }
    

    The containing frame and the part may have RefCounts greater than 0 even after ODFrame's ReleaseAll() is called. This is because other objects may be keeping a reference to the containing frame or the part.

What Are the Implications on ODPersistentObjects?

When the RefCount of a persistent object goes from 1 to 0, the part has to call its factory object (that is ODDraft). The code looks something like this:

void MyPartRelease(MyPart* somSelf, Environment* ev)
{
  parent_Release(ev); // Which calls ODRefCntObject's Release(ev)

  if (somSelf->GetRefCount(ev) == 0)
      _fDraft->ReleasePart(ev, somSelf);
}

As mentioned above, ODDraft does not delete the released object immediately. Therefore, a part does not need to do any shutting down or deallocation when its RefCount goes down to 0. They are then done in ReleaseAll() and the destructor (that is, somUninit).

One should not consider a persistent object with a zero refcount a dead object because at any point the draft may increment its refcount and hand it to some other objects. Having a refcount of 0 simply means no one has a reference to the object.

However, if a part needs to function differently when there is no reference to it, it can use the RefCount to identify the situation and respond appropriately. For example, it may choose to get rid of any structure or service that is not needed anymore. For example, a communication part may choose to close its driver when there is no reference to it.

void CommPartRelease(CommPart* somSelf, Environment* ev)
{
  parent_Release(ev);
  if (somSelf->GetRefCount(ev) == 0)
  {
     CloseDriver();
     fDraft->ReleasePart(ev, somSelf);
  }
}

If the object does something other than calling its factory object when its RefCount goes down to 0, it needs to reinitialize itself when its RefCount is bumped from 0 to 1. This is being done in the object's Acquire() call. In general, the object should undo what it did in Release() when the RefCount went down to 0.

The following is an example of what the above-mentioned CommPart would do in response to its RefCount going from 0 to 1.

void CommPartAcquire(CommPart* somSelf, Environment* ev)
{
  parent_Acquire(somSelf, ev);
  if (somSelf->GetRefCount(ev) == 1)
  {
     OpenDriver();
  }
}

Persistent References

   

A persistent reference is used in a storage unit value to refer to another storage unit in the same document. This reference is persistent in the sense that it is preserved across sessions.

References are used in many places in OpenDoc:

  1. The draft has a list of references to the root frames. When a draft is opened, the root parts can use the frames to construct their windows.

  2. Frame has a persistent reference to its part and the part in turn has a persistent reference to its display and embedded frames. Persistent references to embedded frames are essential to part embedding.

  3. Parts need persistent references for hierarchical storage.

ODStorageUnitRef

This persistent reference in OpenDoc is typed ODStorageUnitRef. ODStorageUnitRef is a 32-bit value. One should never try to inspect or interpret a ODStorageUnitRef. ODStorageUnit and ODStorageUnitView provides a set of methods to manipulate ODStorageUnitRef.

           void  GetStrongStorageUnitRef (ODStorageUnit* embeddedSU,
                                          ODStorageUnitRef ref);
           void  GetWeakStorageUnitRef   (ODStorageUnit* embeddedSU,
                                          ODStorageUnitRef ref);
      ODBoolean  IsStrongStorageUnitRef  (ODStorageUnitRef ref);
      ODBoolean  IsWeakStorageUnitRef    (ODStorageUnitRef ref);
  ODStorageUnit* RemoveStorageUnitRef    (ODStorageUnitRef ref);
ODStorageUnitID  GetIDFromStorageUnitRef (ODStorageUnitRef ref);
      ODBoolean  IsValidStorageUnitRef   (ODStorageUnitRef ref);

ODStorageUnitRef can only be created from a ODStorageUnit which is focused to a value or a ODStorageUnitView (is to be focused to a value). The scope of the ODStorageUnitRef is then limited to the value from which it is created. It is illegal and dangerous to use ODStorageUnitRef in a different value. If used, it will almost certainly not refer to the correct storage unit rendering the resulting behavior unpredictable.

One can create virtually unlimited number of References in a storage unit value.

If a persistent reference is no longer needed, one should remove it from the value from which it is created. It is crucial to have unnecessary persistent references removed for efficiency, robustness and effective garbage collection.

ODStorageUnitRef is not recycled. Therefore, all ODStorageUnitRefs from a value are guaranteed to be unique.

The referring power of ODStorageUnitRef are preserved across sessions. One can write out a ODStorageUnitRef in the value, close and reopen the container and use the ODStorageUnitRef in that value to find the referred storage unit.

Strong and Weak ODStorageUnitRef

           

There are two kinds of ODStorageUnitRef: strong and weak. Their difference is important when a clone operation is performed using ODDraft::BeginClone and ODDraft::EndClone. In a clone operation, all the storage units referenced by a strong ODStorageUnitRef are copied. Consider the following example,

See Figure 85

Sample Codes

Creating an auxiliary storage unit for a part:

void MyPart::InitPart(Environment* ev, ODStorageUnit* storageUnit)
{
  ...

  // Create the Aux storage unit
  ODStorageUnit* auxStorageUnit =
                 storageUnit->GetDraft(ev)->CreateStorageUnit(ev);

  // Add a property and value to store the persistent reference
  storageUnit->AddProperty(ev, kODPropContents);
  storageUnit->AddValue(ev, kODStrongStorageUnitRef);

  // storageUnit is focused to the newly created value already.
  // Create a persistent reference to the Aux storage unit in the
  // context of the create value.
  ODStorageUnitRef ref;
  storageUnit->GetStrongStorageUnitRef(ev, auxStorageUnit, ref);

  // The focus is unchanged and it is still pointing to the
  // created value.
  // Write out the persistent reference to the value.
  ODByteArray ba;
  ba._length = sizeof(ODStorageUnitRef);
  ba._maximum = sizeof(ODStorageUnitRef);
  ba._buffer = &ref;
  storageUnit->SetValue(ev, &ba);
   ...
}

Let's say that the part is instantiated later in a different session, the part can retrieve its auxiliary storage unit as follows:

void MyPart::InitPartFromStorage(Environment* ev, ODStorageUnit* storageUnit)
{
  ...

  // Focus the storage unit to the right value
  storageUnit->Focus(ev,
                     kODPropContents,
                     kODPosUndefined,
                     kODStrongStorageUnitRef,
                     0,
                     kODPosUndefined);

  // Retrieve the persistent reference
  ODByteArray ba;
  storageUnit->GetValue(ev, sizeof(ODStorageUnitRef), &ba);

  // Use the retrieved persistent reference to get the ID for
  // the storage unit
  ODStorageUnitID ID =
    storageUnit->GetIDFromStorageUnitRef(ev,
                                         *((ODStorageUnitRef*) ba._buffer));

  // Dispose of the buffer
  ODDisposePtr(ba._buffer);

  // Use the ID to get the Aux storage unit from the draft.
  ODStorageUnit* auxStorageUnit =
                 storageUnit->GetDraft(ev)->GetStorageUnit(ev, id);
  ...
}

Storage Unit Manipulation Recipes/Examples

   

This section describes the basic usage of ODStorageUnit with actual examples and recipes. It is intended to supplement the class documentation. For detailed description of the ODStorageUnit methods, refer to the OpenDoc Programming Reference.    

Recipe 1:

Given a ODStorageUnit, create multiple properties and values.

su->AddProperty(ev, kODPropContents);
su->AddValue(ev, kMyKind);
su->AddValue(ev, kKindTEXT);
su->AddValue(ev, kKindPICT);
su->AddProperty(ev, kPropAnnotations);
su->AddValue(ev, kMyAnnotationsType);

Recipe 2:

Given the ODStorageUnit in Recipe 1, set the focus to a certain value.

There is more than one way to get to the value:

  1. Focus the ODStorageUnit to the value using the known property and type.

    su->Focus(ev,
              kODPropContents,   // Property name
              kODPosUndefined,
              kMyKind,           // Value type
              0,
              kODPosUndefined);
    

  2. Focus the ODStorageUnit to the value using the known property and value index.

    su->Focus(ev,
              kODPropContents,   // Property name
              kODPosUndefined,
              kODNULL,
              1,                 // Value index
              kODPosUndefined);
    

  3. Focus the ODStorageUnit to the value using the known property and relative position.

    su->Focus(ev,
              kODPropContents,   // Property name
              kODPosUndefined,
              kODNULL,
              0,
              kODPosFirstSib);   // Position code
    

  4. Focus the ODStorageUnit to the value using a ODStorageUnitCursor. This method is especially useful when one needs to focus to a property or a value frequently.

There are two ways to create a cursor:

One can then focus a storage unit using the created cursor:

su->FocusWithCursor(ev, cursor);

Recipe 3:

Given the ODStorageUnit from Recipe 1, iterate through all the properties.

// Unfocus storage unit
su->Focus(ev,
          kODNULL,
          kODPosAll,         // Position code
          kODNULL,
          0,
          kODPosAll);        // Position code

// Get the number of properties
ODULong numProperties = su->CountProperties(ev);

// Iterate through the properties
for (i = 1; i <= numProperties; i++)
{
  su->Focus(ev,
            kODNULL,
            kODPosNextSib,      // Position code
            kODNULL,
            0,
            kODPosUndefined);
}

Recipe 4:

Given the ODStorageUnit from Recipe 1, iterate through all the values in kODPropContents property.

// Focus to the desired property
su->Focus(ev,
          kODPropContents,   // Property name
          kODPosUndefined,
          kODNULL,
          0,
          kODPosAll);        // Position code

// Get the number of values
ODULong numValues = su->CountValues(ev);

// Iterate through the values
for (i = 1; i <= numValues; i++)
{
su->Focus(ev,
          kODNULL,
          kODPosSame,        // Position code
          kODNULL,
          0,
          kODPosNextSib);    // Position code
}

Recipe 5:

Given the ODStorageUnit from Recipe 1, remove the kPropAnnotationsProperty.

// Focus to the desired property
su->Focus(ev,
          kPropAnnotations,
          kODPosUndefined,
          kODNULL,
          0,
          kODPosAll);

// Remove the property
su->Remove(ev);

Recipe 6:

Given the ODStorageUnit from Recipe 1, remove the kKindPICT value in kODPropContents Property.

// Focus to the desired value
su->Focus(ev,
          kODPropContents,
          kODPosUndefined,
          kODNULL,
          kKindPICT,
          kODPosUndefined);

// Remove the value
su->Remove(ev);

Recipe 7:

Given the focused ODStorageUnit from Recipe 2, manipulate the data in the value. One can think of a value as a stream. For storage units to work in a distributed environment, all the data passed in or out through the storage unit function are in ODByteArray. The following is the structure of an ODByteArray (from the IDL emitter):

typedef struct
{
  unsigned long _maximum;
  unsigned long _length;
  octet *_buffer;
} _IDL_SEQUENCE_octet;

typedef _IDL_SEQUENCE_octet ODByteArray;

where:

To add data at a particular position:

su->SetOffset(ev, desiredPosition);

// Set up byte array
ODByteArray ba;
ba._length = length;         // Length of data to be written
ba._maximum = maximum;       // Maximum size of buffer
ba._buffer = buffer;         // Pointer to the data
su->SetValue(ev, &ba);

To add data at the current offset:

// No SetOffset is needed.
// Just call SetValue.
su->SetValue(ev, &ba);

To insert data at a particular position:

su->SetOffset(ev, desiredPosition);
su->InsertValue(ev, &ba);

To append data at the end of a value:

// Get the length of the value
ODULong offset = su->GetSize(ev);

// Set the offset to the end of the value
su->SetOffset(ev, offset);
su->SetValue(ev, &ba);

To remove data from a particular position:

su->SetOffset(ev, offset);
su->DeleteValue(ev, length);

To get data from a particular position:

// Get to the right position
su->SetOffset(ev, offset);

// Set up byte array.
// There is really nothing to do here because the fields of
// ODByteArray are going to be filled out by GetValue.
ODByteArray ba;

// Get the data
su->GetValue(ev, desiredLength, &ba);

// Dispose the buffer created by GetValue when done
ODDisposePtr(ba._buffer);

Persistent Objects Removal

A persistent object has two states:

The persistent state is stored in the associated storage unit while the runtime state is stored in the corresponding object instance.

Release versus Removal

When an object is released, only the runtime state is affected. For example, when the reference count of a persistent object goes down to 0 and is deleted, only the runtime state is destroyed while the persistent state remains unchanged.

However, when an object is removed, both the runtime and the persistent state are destroyed, which means the object instance and the storage unit cease to exist.

Removal without using draft function

Even though OpenDoc provides function to remove persistent objects created by the draft, a part editor does not need to remove any persistent objects using this function. This is because the underlying container suite is responsible for garbage collecting storage units that are no longer in use. A storage unit is not in use when it is no longer transitively strongly referenced by the draft properties storage unit.

Therefore, to remove a persistent object, all the part editor needs to do is to break or remove its reference to the storage unit of the persistent object.

// Focus to the value where the reference is to be removed.
partSU->Focus(...);

// Remove the storage unit ref
partSU->RemoveStorageUnitRef(ev, ref);

The garbage collection scheme is completely dependent on the container suite. Therefore, make sure that you write out references to the storage units of all of the desired persistent objects during externalize.

Removal using OpenDoc function

There are times when a part wants to remove a persistent object explicitly without relying on garbage collection. Here are a couple of examples:

Using the ODDraft function does not guarantee a successful removal. If the persistent object has a reference count of more than 1 when Remove is called, an exception will be thrown. It is the client's responsibility to anticipate and respond to the exception.

The following is a simple code fragment on the function usage:

SOM_TRY
 draft->RemovePart(ev, part);
SOM_CATCH_ALL
 // Some error handling
SOM_ENDTRY

Alternate Container Suite Use

 

The OpenDoc storage system is designed to work with multiple container suites. In addition to the bento container suite, other vendors might want to provide alternate container suites.

Creating a Container

   

To create a container, the client has to call the following ODStorageSystem method:

ODContainer CreateContainer(in ODContainerType containerType,
                            in ODContainerID id);

The first parameter of the method identifies the type of the container. To create a bento file container, the in parameter needs to be kODBentoFileContainer and the second parameter the FSSpec of the file where the container is to be created.

This binding is done at runtime. OpenDoc binding scans the editors folder(s) for container suite libraries. Each container suite library contains a nmap resource which lists the type(s) of containers that the container suite can create. OpenDoc binding then caches this information and uses it to determine which container suite library to use when ODStorageSystem::CreateContainer is called.

The following is the nmap resource of the bento container suite as defined in a private resource file:

#define kNMAPid1 128
#define kODBentoFileContainer  "OSA:Bento:ContainerType:File"
#define kODBentoMemoryContainer  "OSA:Bento:ContainerType:Memory"
#define kODFileContainerID    "ODFileContainer"
#define kODMemoryContainerID   "ODMemContainer"

// kODNameMappings, kODContainerSuite and
// kODIsAnISOString are defined by OpenDoc.
// They can be found in StdDef.r.
resource kODNameMappings (kNMAPid1)
{
  kODContainerSuite,
  {
    /* Array types: 2 elements */
    /* [1] */
    kODBentoFileContainer,
    kODIsAnISOString
    {
      kODFileContainerID
    },
    /* [2] */
    kODBentoMemoryContainer,
    kODIsAnISOString
    {
      kODMemoryContainerID
    }
  }
};

As it is outlined in the resource, the bento container suite can create two kinds of containers: file and memory.

Other container suite providers need to include this nmap resource into their container suite libraries. Otherwise, their container suite is never bound and created.

Acquiring a Container

The process of acquiring a container is almost the same as creating a container. The following ODStorageSystem method is used to acquire a previously created container:

ODContainer AcquireContainer(in ODContainerType containerType,
                             in ODContainerID ID);

The same process takes place to bind the container suite library for the container type supplied.

Default Container Type

The default container types are interpreted as the bento container types. Therefore, supplying kODDefaultFileContainer to AcquireContainer or CreateContainer results in a bento file container being instantiated. Similarly, supplying kODDefaultMemoryContainer to the same methods results in a bento memory container being instantiated.

Bento Container Suite

 

A container suite is an implementation of several storage classes (ODContainer, ODDocument, ODDraft, and ODStorageUnit) which provides the main functionality of of the OpenDoc storage system. The functionality includes the maintanence of documents, drafts, persistent objects and storage units.

The bento container suite (BCS) is a container suite based on bento (container manager) technology.

The bento container suite currently supports two kinds of containers: file container and in-memory containers File containers are persistent across sessions while in-memory containers are ephemeral.

How to Create a BCS File Container?

   

In OpenDoc, every container is created through the ODStorageSystem.

ODByteArray ba;

// Set the length to the size of the file spec which
// contains meaningful data
ba._length = sizeof(short) + sizeof(long) + fsSpec->name[0] + 1;
ba._maximum = sizeof(FSSpec);
// fsSpec is the FSSpec* of the file to be opened
ba._buffer = fsSpec;

ODContainer* newContainer =
             session->GetStorageSystem(ev)->
             CreateContainer(ev,kODDefaultFileContainer,&ba);

How to Create a BCS In-Memory Container?

In OpenDoc, every container is created through the ODStorageSystem.

ODByteArray ba;
ba._length = sizeof(ODHandle);
ba._maximum = sizeof(ODHandle);

// Handle is the Handle where the bento container is going to live
ba._buffer = &handle;

ODContainer* newContainer = session->GetStorageSystem(ev)->
   CreateContainer(ev, kODDefaultMemoryContainer, ba);

How to Get the FSSpec from a BCS File Container?

This is not recommended because there is no way to tell whether a container is a BCS file container or not. However, if you are sure that the container is a BCS file container and you really need to know the FSSpec, here is how you do it.

// Get the container ID
ODContainerID containerID = container->GetID(ev);

// The returned container ID contains the FSSpec in _buffer.
// The returned ID may have _length smaller than sizeof(FSSpec).
FSSpec* fsSpec = (FSSpec*) containerID->_buffer;

// Use it
 ...

// Dispose the _buffer in the byte array (that is, the container ID)
ODDisposePtr(containerID->_buffer);

Note:

This is not recommended.

Garbage Collection

The bento container suite supports garbage collection. It is triggered when a draft is saved. To not to lose your persistent objects, you should create references to all the persistent objects you are using during externalize.


[ Top | Previous | Next | Contents | Index | Documentation Homepage ]