[Previous] [Contents] [Next] [IONA Technologies]


Chapter 23
Loaders


Contents

23.1 Specifying a Loader for an Object
23.2 Connection between Loaders and Object Naming
23.3 Loading Objects
23.4 Saving Objects
23.5 Aspects of Writing a Loader
23.6 Example Loader
23.7 Polymorphism
23.8 Approaches to Providing Persistent Objects
23.9 Disabling the Loaders



When an operation invocation arrives at a server process, OrbixWeb searches for the target object in the process's internal object table. By default, if the object is not found, OrbixWeb returns an exception to the caller. However, if one or more loader objects are installed in the process, these will be informed about the object fault and provided with an opportunity to load the target object and resume the invocation transparently to the caller. The loaders are maintained in a chain, and are tried in turn until one can load the object; if no loader can load the object, an exception is returned to the caller.

Loaders can provide support for persistent objects–long lived objects stored on disk in the file system or in a database.

Loaders are also called when an object reference enters an address space, and not just when a "missing object" is the target of a request. This can arise in a number of ways:

The loaders are given an opportunity to respond to such object faults by loading the target object of the reference into the process's address space. If no loader can load the referenced object, OrbixWeb will construct a proxy for the object.

Overview of Creating a Loader

Loaders are coded by defining a derived class of LoaderClass (defined in package IE.Iona.Orbix2.CORBA), and they are installed by creating an instance of that new class. LoaderClass provides the following methods:

Full details of class LoaderClass are given in Part II, "Class and Interface Reference" of the OrbixWeb Reference Guide.

The constructor of LoaderClass (the base class of all loaders) takes an optional boolean parameter. When creating a loader object, this parameter must be true if the load() method of the new loader is to be called by OrbixWeb.

Section 23.6 shows an example loader, but first the proceeding sections explain the different aspects of the loader mechanism in more detail.

23.1 Specifying a Loader for an Object

Each object is associated with a loader object, which is informed when the object is named or renamed (section 23.2) and when the object is to be saved (section 23.4). If no loader is explicitly specified for an object, then it is associated with a default loader, implemented by OrbixWeb.

An object's loader can be specified as the object is being created, either by the TIE or by the BOAImpl approach.

In the TIE approach, this can be done by passing the loader object as the third parameter to a TIE object constructor. For example,

In the BOAImpl approach, this can be done by declaring the implementation class's constructor to take a loader object parameter; and by defining this constructor to pass on this object as the second parameter to its BOAImpl class's constructor. For example:

Each object is associated with a simple default loader if no loader is specified for it. This loader does not support persistence.

An object's loader can be retrieved by calling:

23.2 Connection between Loaders and Object Naming

When supporting persistent objects, it is often important to control the markers that are assigned to them. For example, it is frequently a requirement to use an object's marker as a key to search for its persistent data; and the format of these keys will depend on how the persistence is implemented by the loader. Therefore, it is common for loaders to choose object markers, or at least to be given the chance to accept or reject markers chosen by application level code.

Recall that objects can be named in a number of ways:

In all cases, OrbixWeb calls the object's loader to confirm the chosen name, thus giving the loader a chance to override the choice. In the first two cases above, OrbixWeb calls record(); in the last case it calls rename() because the object already exists.

OrbixWeb executes the following algorithm when an object is created, or an object's existing marker is changed:

Both record() and rename() can, if necessary, raise an exception.

The implementations of rename() and record() in LoaderClass both return without changing the suggested name. (It's implementation of load() and save() carry out no actions.)

The default loader (which is associated with all objects which are not explicitly associated with another loader) is an instance of NullLoaderClass, a derived class of LoaderClass. This class inherits load(), save() and rename() from LoaderClass. It implements record() so that if no marker name is suggested it chooses one that is a string of decimal digits, different to any it generated before in the current process.

23.3 Loading Objects

When an object fault occurs, the load() method is called on each loader in turn until one of them successfully returns the address of the object, or until they have all returned null.

The responsibilities of the load() method are:

The load() method is given the following information:

The interface name of the missing object is determined as follows:

23.4 Saving Objects

An application programmer may invoke the method _CORBA.Orbix.finalize() before the application exits. If this method is invoked, then OrbixWeb iterates through all of the objects in its object table and calls the save() method on the loader associated with each object. A loader may save the object to persistent storage (either by calling a method on the object, or by accessing the object's data and writing this data itself).

A programmer can also explicitly cause the save() method to be called by invoking an object's _save() method. The _save() method simply calls the save() method on the object's loader. _save() must be called in the same address space as the target object: calling it in a client process, that is, on a proxy, will have no effect.

The two alternative reasons for the invocation of save() are distinguished by its second parameter. This parameter is of type int, and takes one of the following values:


_CORBA.processTermination

The process is about to exit.

_CORBA.objectDeletion

The method BOA.dispose() method has been called on the object.

_CORBA.explicitCall

The object's _save() method has been called.

23.5 Aspects of Writing a Loader

If a loader is being written for a specific interface, then the following actions are normally carried out:

23.6 Example Loader

This section presents a simple loader for one IDL interface. A version of the code for this example is given in the per_simp demonstration directory.

Two interfaces are involved in the application:

Throughout this simple example, we will assume that these definitions have been compiled using the IDL -jP switch as follows:

The classes output by the IDL compiler are within the scope of the per_simp Java package.

Interfaces Account and Bank are implemented by classes AccountImplementation and BankImplementation, respectively. Instances of class AccountImplementation are made persistent using a loader (of class Loader). The persistence mechanism used is very primitive because it uses one file per account object. Nevertheless, the example acts as a simple introduction to loaders. The implementation of class Loader is shown later, but first the implementations of classes AccountImplementation and BankImplementation are shown.

Class AccountImplementation is implemented as follows:

Two methods have been added to the implementation class. The static method loadMe() is called from the load() method of the loader. It is given the name of the file to load the account from. The method saveMe() writes the member variables of an account to a specified file. These methods can be coded as follows:

The statement:

in AccountImplementation.loadMe() creates a new TIE for the implementation object accImpl, and specifies its marker to be file_name and its loader to be the loader object referenced by parameter loader (Actually, our example creates only a single loader object, as we will see.)

Class BankImplementation is implemented as follows:

A single loader object, of class Loader, is created in the main method and each Account object created is assigned this loader. Each BankImplementation object holds its sort code (a unique number for each bank, for example 1234), and also a reference to the loader object to associate with each Account object as it is created. Each account is assigned a unique account number, constructed from its bank's sort code and a unique counter value. The first account in the bank with sort code 1234 is therefore given the number "a1234-1". The marker of each account is its account number, for example "a1234-1". This ability to choose markers is an important feature for persistence.

The statement:

creates a new TIE for the AccountImplementation object assigning it the marker accountNr and the loader referenced by m_loader. (The bank objects are not associated with an application level loader, so they are implicitly associated with the OrbixWeb default loader.)

The server application class must create a loader and a bank; for example:

Coding the Loader

Class Loader can be implemented as follows:

Note that the constructor of LoaderClass takes a parameter to indicate whether or not the loader being created should be included in the list of loaders that are tried when an object fault occurs. By default, this value is false; so our loader class's constructor passes a value of true to the LoaderClass constructor to indicate that instances of Loader should be added to this list.

Note that the AccountImplementation.loadMe() method assigns the correct marker to the newly created object. If it failed to do this, then subsequent calls on the same object would result in further object faults and calls to the Loader.load() method.

It would have been possible for the Loader.load() method to have read the data itself, rather than calling the static method AccountImplementation.loadMe(). However, to construct the object, load() would be dependent on there being a constructor on class AccountImplementation that takes all of an account's state as parameters. Since this will not be the case for all classes, it is safer to introduce a method such as loadMe(). Equally, Loader.save() could have accessed the account's data and written it out, rather than calling AccountImplementation.saveMe(). However, it would then be dependent on AccountImplementation providing some means to access all of its state.

In any case, having loadMe() and saveMe() within class AccountImplementation provides a sensible split of functionality between the application level class, AccountImplementation, and the loader class.

Loaders are Transparent to Clients

A client that wishes to create a specific account could execute the following:

A client that wishes to manipulate an account could execute the following:

If the target account is not already present in the server then the load() method of the loader object will be called. If the loader recognises the object, it will handle the object fault by re-creating the object from the saved data. If the load request cannot be handled by that loader, then in this case the default loader will be tried next and this will always indicate that it cannot load the object. This will finally result in an INV_OBJREF exception being returned to the caller.

23.7 Polymorphism

Every loader should be written to allow for polymorphism. In particular, the interface name passed to a loader may be a base interface of the actual interface that the target object implements. This may arise, for example, when the client has bound to an object using I1._bind() but where the object's actual interface is in fact a derived interface of I1.

The class of the target object (and therefore how to load it) must therefore be determined either from the marker passed to the loader, or from the data used to load the target object. The demonstration code for loaders shows the marker names being used to distinguish the real interface of an object (using the first character of each marker). This is a simple approach, but it is probably better in a large system to use some information stored with the persistent data of each object.

It must also be remembered that distinguishing the real interface of an object may not be necessary in all applications and for all interfaces. If the correct interface name is always used in calls to _bind() (that is, I1._bind() is always used when binding to an object with interface I1) then handling polymorphism is not required. This will also be the case if _bind() is not used for a given interface: for example, all object references to accounts may be obtained by searching (say, using an owner name) in a bank, rather than using _bind().

It is however possible that, because of programmer error, the actual interface of the target object is not the same or a derived interface of correct one. This should be detected by a loader.

23.8 Approaches to Providing Persistent Objects

There are many ways to use the support described so far in this chapter. This section will outline some of the choices which are available.

The information provided to a loader on an object fault comprises the object's marker and the interface name. The loader must be able to find the requested object using these two pieces of information. It must also be able to determine the implementation class of the target object–so that it can create an object of the correct class. Naturally, this implementation class must implement the required interface or one of its derived interfaces.

It is normal, therefore, to use the marker as a key to find the object, and either to encode the target object's implementation class in the marker, or to first find the object's persistent state and determine the implementation class from that data.

For example, a prefix of the marker could indicate the implementation class and the remainder of the marker (or indeed the whole marker) could be the name of the file that holds the object's persistent state.

The following are some of the choices available when using loaders to support persistent objects:

Many different arrangements are possible for the loaders themselves, for example:

If one loader per interface is used, each loader's load() method will be called in turn until one indicates that it can load the target object. Although this approach is simple to implement, such a linear search may be inefficient if a process handles a large number of interfaces. One efficient mechanism is to install a master loader, with which the other loaders can register. Each registration would give some key that indicates when the registering loader's load() method is to be called by the master loader; a key could be a marker prefix and an interface name.

Another reason for having more than one loader is that a process may use objects from separate subsystems–each of which installs its own loader(s). These loaders must of course be able to distinguish requests to load their own objects. Confusion can be avoided if the subsystems handle disjoint interfaces (recall that the interface name is passed to a loader); however, some co-operation between the subsystems is required if they handle the same interfaces, or interfaces which have a common base interface.1 Each subsystem must be able to distinguish its objects based on their markers or their persistent state.

In particular, the subsystems must choose disjoint markers.

23.9 Disabling the Loaders

On occasion, it is useful to be able to disable the loaders for a period. If, when binding to an object, the caller knows that the object will be already loaded if it exists, then it might be worthwhile to avoid involving the loaders if the object cannot be found.

The loaders can be disabled simply by calling the method:

on the _CORBA.Orbix object, with a false parameter value. This returns the previous setting; the default is to have loaders enabled.



1 If I1 is a base interface of I2 and I3, then the objects of interfaces I2 and I3 must be distinguishable to avoid confusion when "I1" is passed as an interface name to load().



[Roadmap] [Introduction] [GS: Applications] [GS: Applets]
[IDL] [Mapping] [Programming OrbixWeb] [Publishing Objects] [Retrieving Objects] [IIOP]
[Running Clients] [Running Servers] [Exceptions] [Inheritance] [Callbacks] [Contexts]
[API Configuration] [TypeCode] [Any] [DII] [IR] [Filters] [Smart Proxies] [Loaders] [Locators]
[Index]