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