| User's Guide: A Framework for Object Persistency for GNAT; Version 0.6.1; Document Revision $Revision: 1.17 $ | ||
|---|---|---|
| Prev | Chapter 3. ODB Basics | Next |
In order to make an persistent object really persistent, the object has to be named. Otherwise, the application would not be able to retrieve the object.
Upon startup of an application using ODB, all types which are intended to be stored in the object store are registered in the ODB.Persistent package. Together with the type name, the pointer to a procedure is stored. This procedure is called Factory and is used to create an object of the given type in the memory.
When reading in an object from the object store, the type name is looked up in the list of all registered factories and the factory is called to create the actual instance.
function Factory return Persistent.Reference is
Result : Reference := new Object;
begin
Handle( Result ).Data := new Object_Data_Type;
return Result;
end;
Important to note is that the new operation is used together with
the Reference type. This forces the object to be allocated in the
storage_pool of the object management.
In addition to the registration of the Factory for the class, the attributes have to be declared for the object implementation.
Attribute names are used to map fields of an Ada 95 object between data entries in the storage. As a consequence fields may be added to the object during development of the application and the object stay still loadable.

The sequence of calls when loading an object from the object
Object are loaded from the storage by means of the Deserialize proceudure. This is an abstract procedure which has to be provided by the implementation:
procedure Deserialize(
Item : in out Object;
Header : in Storage_Header.Handle;
S : in Stream_Access );
The purpose of this function is to read in the object attributes from
the given stream. The storage_header contains the fields and the offset
of the attributes within the memory stream.
procedure Deserialize(
Item : in out Object;
Header : in Storage_Header.Handle;
S : in Stream_IO.Stream_Access ) is
Field : String_Array.Handle := Attributes( Header.all );
begin
for i in Field'Range loop
declare
ID : Natural;
Offset : Natural;
Name : constant String := To_String( Field(i) );
begin
ID := Classes.Attribute( Object'Tag, Name );
if ID /= 0 then
Offset := Storage_Header.Lookup_Attribute( Header.all, Name );
Read_Offset( S, Offset );
case ID is
when D_Name =>
Item.Name := Unbounded_String'Input(S);
when D_Street =>
Item.Street := Unbounded_String'Input(S);
............
when Others =>
null;
end case;
end if;
exception
when Storage_Header.Unknown_Attribute =>
null;
end;
end loop;
String_Array.Free( Field );
end Deserialize;
This procedure reads in all attributes which have been listed in the
object header. For each field in the header registered field id and the
offset in the object storage is looked up. The read pointer is set to
the found offset and the data type is read in. If a attribute name is
not known in the class the field will be ignored.
During startup of the application the package will register the attribute names and the corresponding id by the following code fragment:
Class_Id := Classes.Register_Factory( Object'Tag, Factory'Access );
Classes.Attribute( Class_Id, "D_Name", D_Name );
Classes.Attribute( Class_Id, "D_Used", D_Used );
Classes.Attribute( Class_Id, "D_Pairs", D_Pairs );
When an application decides to terminate it self, the application may decide to store all persistent objects into a persistent storage media by calling the procedure Save.
When calling the procedure Save (e.g. from the component ODB.Storage.File), all named objects are stored on a permanent storage media. This is done by running through a table which contains all persistent information.

The sequence of calls when saving a object to the object storage.
Objects are written by means of the Serialize procedure into a temporary work space, from where the complete object written out into a storage media.
procedure Serialize(
Item : in out Object;
Header : in Storage_Header.Handle;
S : in Stream_Access ) is abstract;
The purpose of this procedure is to write the contents of the attributes
into the object storage and the storing the offset of each attribute
in the storage header information of the object.
procedure Serialize(
Item : in out Object;
Header : in Storage_Header.Handle;
S : in Stream_IO.Stream_Access ) is
begin
Register_Attribute( Header.all, D_Street, Write_Offset( S ), Object'Tag );
Unbounded_String'Output( S, Item.Street );
Register_Attribute( Header.all, D_Name, Write_Offset( S ), Object'Tag );
Unbounded_String'Output( S, Item.Name );
.......
end Serialize;
In order to simplify the development, the odl translator generates
automatically such procedures.As already mentioned previously the implementation of the read and write procedures have to be symetric, which means what has been written by the Searialize procedure has to to be readable by the Deserialize procedures. Besides of this fact, there are some basic rules to be followed:
References to other objects can only be stored as references to objects. ODB.Persistent provide a Read/Write method for this type and will resolve the references to other objects in the object store automatically.
Dynamic data structures have to be resolved by the object implementation, e.g. as in the previous example the array of pairs R.Pairs.
Any access types in the object have to be resolved by the object implementation (e.g. the ODB.Collection. class)
For reading and writing use always the operations Input/Output.
Since the ODL translator is available under normal circumstance the implementation of the read/write procedure by hand is not nessescary since the ODL translator creates the code is self.