Facets
Facets provide a general-purpose mechanism for non-intrusively extending the type system of an application, by loosely coupling new type instances to existing ones. This shifts the type selection process from compile to run time and implements a form of late binding. This is particularly useful for versioning an application.
On this page:
Ice Objects as Collections of Facets
Up to this point, we have presented an Ice object as a single conceptual entity, that is, as an object with a single most-derived interface and a single identity, with the object being implemented by a single servant. However, an Ice object is more correctly viewed as a collection of one or more sub-objects known as facets, as shown below:
An Ice object with five facets sharing a single object identity.
The diagram above shows a single Ice object with five facets. Each facet has a name, known as the facet name. Within a single Ice object, all facets must have unique names. Facet names are arbitrary strings that are assigned by the server that implements an Ice object. A facet with an empty facet name is legal and known as the default facet. Unless you arrange otherwise, an Ice object has a single default facet; by default, operations that involve Ice objects and servants operate on the default facet.
Note that all the facets of an Ice object share the same single identity, but have different facet names. Recall the definition of Ice::Current
once more:
module Ice { local dictionary<string, string> Context; enum OperationMode { Normal, \Nonmutating, \Idempotent }; local struct Current { ObjectAdapter adapter; Identity id; string facet; string operation; OperationMode mode; Context ctx; int requestId; EncodingVersion encoding; }; };
By definition, if two facets have the same id
field, they are part of the same Ice object. Also by definition, if two facets have the same id
field, their facet
fields have different values.
Even though Ice objects usually consist of just the default facet, it is entirely legal for an Ice object to consist of facets that all have non-empty names (that is, it is legal for an Ice object not to have a default facet).
Each facet has a single most-derived interface. There is no need for the interface types of the facets of an Ice object to be unique. It is legal for two facets of an Ice object to implement the same most-derived interface.
Each facet is implemented by a servant. All the usual implementation techniques for servants are available to implement facets — for example, you can implement a facet using a servant locator. Typically, each facet of an Ice object has a separate servant, although, if two facets of an Ice object have the same type, they can also be implemented by a single servant (for example, using a default servant).
Server-Side Facet Operations
On the server side, the object adapter offers a number of operations to support facets:
namespace Ice { dictionary<string, Object> FacetMap; local interface ObjectAdapter { Object* addFacet(Object servant, Identity id, string facet); Object* addFacetWithUUID(Object servant, string facet); Object removeFacet(Identity id, string facet); Object findFacet(Identity id, string facet); FacetMap findAllFacets(Identity id); FacetMap removeAllFacets(Identity id); // ... }; };
These operations have the same semantics as the corresponding "normal" operations for servant activation and deactivation (add
, addWithUUID
, remove
, and find
), but also accept a facet name. The corresponding "normal" operations are simply convenience operations that supply an empty facet name. For example, remove(id)
is equivalent to removeFacet(id, "")
, that is, remove(id)
operates on the default facet.
findAllFacets
returns a dictionary of <facet-name, servant>
pairs that contains all the facets for the given identity.
removeAllFacets
removes all facets for a given identity from the active servant map, that is, it removes the corresponding Ice object entirely. The operation returns a dictionary of <facet-name, servant>
pairs that contains all the removed facets.
These operations are sufficient for the server to create Ice objects with any number of facets. For example, assume that we have the following Slice definitions:
module Filesystem { // ... interface File extends Node { idempotent Lines read(); idempotent void write(Lines text) throws GenericError; }; }; module FilesystemExtensions { // ... class DateTime extends TimeOfDay { // ... }; struct Times { DateTime createdDate; DateTime accessedDate; DateTime modifiedDate; }; interface Stat { idempotent Times getTimes(); }; };
Here, we have a File
interface that provides operations to read and write a file, and a Stat
interface that provides access to the file creation, access, and modification time. (Note that the Stat
interface is defined in a different module and could also be defined in a different source file.) If the server wants to create an Ice object that contains a File
instance as the default facet and a Stat
instance that provides access to the time details of the file, it could do so as follows:
// Create a File instance. // Filesystem::FilePtr file = new FileI; // Create a Stat instance. // FilesystemExctensions::DateTimePtr dt = new FilesystemExtensions::DateTime; FilesystemExtensions::Times times; times.createdDate = dt; times.accessedDate = dt; times.modifiedDate = dt; FilesystemExtensions::StatPtr stat = new StatI(times); // Register the File instance as the default facet. // Filesystem::FilePrx filePrx = myAdapter->addWithUUID(file); // Register the Stat instance as a facet with name "Stat". // myAdapter->addFacet(stat, filePrx->ice_getIdentity(), "Stat");
The first few lines simply create and initialize a FileI
and StatI
instance. (The details of this do not matter here.) All the action is in the last two statements:
Filesystem::FilePrx filePrx = myAdapter->addWithUUID(file); myAdapter->addFacet(stat, filePrx->ice_getIdentity(), "Stat");
This registers the FileI
instance with the object adapter as usual. (In this case, we let the Ice run time generate a UUID as the object identity.) Because we are calling addWithUUID
(as opposed to addFacetWithUUID
), the instance becomes the default facet.
The second line adds a facet to the instance with the facet name Stat
. Note that we call ice_getIdentity
on the File
proxy to pass an object identity to addFacet
. This guarantees that the two facets share the same object identity.
Note that, in general, it is a good idea to use ice_getIdentity
to obtain the identity of an existing facet when adding a new facet. That way, it is guaranteed that the facets share the same identity. (If you accidentally pass a different identity to addFacet
, you will not add a facet to an existing Ice object, but instead register a new Ice object; using ice_getIdentity
makes this mistake impossible.)
Client-Side Facet Operations
On the client side, which facet a request is addressed to is implicit in the proxy that is used to send the request. For an application that does not use facets, the facet name is always empty so, by default, requests are sent to the default facet.
The client can use a checkedCast
to obtain a proxy for a particular facet. For example, assume that the client obtains a proxy to a File
instance as shown above. The client can cast between the File
facet and the Stat
facet (and back) as follows:
// Get a File proxy. // Filesystem::FilePrx file = ...; // Get the Stat facet. // FilesystemExtensions::StatPrx stat = FilesystemExtensions::StatPrx::checkedCast(file, "Stat"); // Go back from the Stat facet to the File facet. // Filesystem::FilePrx file2 = Filesystem::FilePrx::checkedCast(stat, ""); assert(file2 == file); // The two proxies are identical.
This example illustrates that, given any facet of an Ice object, the client can navigate to any other facet by using a checkedCast
with the facet name.
If an Ice object does not provide the specified facet, checkedCast
returns null:
FilesystemExtensions::StatPrx stat = FilesystemExtensions::StatPrx::checkedCast(file, "Stat"); if (!stat) { // No Stat facet on this object, handle error... } else { FilesystemExtensions::Times times = stat->getTimes(); // Use times struct... }
Note that checkedCast
also returns a null proxy if a facet exists, but the cast is to the wrong type. For example:
// Get a File proxy. // Filesystem::FilePrx file = ...; // Cast to the wrong type. // SomeTypePrx prx = SomeTypePrx::checkedCast(file, "Stat"); assert(!prx); // checkedCast returns a null proxy.
If you want to distinguish between non-existence of a facet and the facet being of the incorrect type, you can first obtain the facet as type Object
and then down-cast to the correct type:
// Get a File proxy. // Filesystem::FilePrx file = ...; // Get the facet as type Object. // Ice::ObjectPrx obj = Ice::ObjectPrx::checkedCast(file, "Stat"); if (!obj) { // No facet with name "Stat" on this Ice object. } else { FilesystemExtensions::StatPrx stat = FilesystemExtensions::StatPrx::checkedCast(file); if (!stat) { // There is a facet with name "Stat", but it is not // of type FilesystemExtensions::Stat. } else { // Use stat... } }
This last example also illustrates that
StatPrx::checkedCast(prx, "")
is not the same as
StatPrx::checkedCast(prx)
The first version explicitly requests a cast to the default facet. This means that the Ice run time first looks for a facet with the empty name and then attempts to down-cast that facet (if it exists) to the type Stat
.
The second version requests a down-cast that preserves whatever facet is currently effective in the proxy. For example, if the prx
proxy currently holds the facet name "Joe", then (if prx
points at an object of type Stat
) the run time returns a proxy of type StatPrx
that also stores the facet name "Joe".
It follows that, to navigate between facets, you must always use the two-argument version of checkedCast
, whereas, to down-cast to another type while preserving the facet name, you must always use the single-argument version of checkedCast
.
You can always check what the current facet of a proxy is by calling ice_getFacet
:
Ice::ObjectPrx obj = ...; cout << obj->ice_getFacet() << endl; // Print facet name
This prints the facet name. (For the default facet, ice_getFacet
returns the empty string.)
Facet Exception Semantics
The common exceptions ObjectNotExistException
and FacetNotExistException
have the following semantics:
ObjectNotExistException
This exception is raised only if no facets exist at all for a given object identity.
FacetNotExistException
This exception is raised only if at least one facet exists for a given object identity, but not the specific facet that is the target of an operation invocation.
If you are using servant locators or default servants, you must take care to preserve these semantics. In particular, if you return null from a servant locator's locate
operation, this appears to the client as an ObjectNotExistException
. If the object identity for a request is known (that is, there is at least one facet with that identity), but no facet with the specified name exists, you must explicitly throw a FacetNotExistException
from locate
instead of simply returning null.