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:
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:
These operations have the same semantics as the corresponding "normal" operations for servant activation and deactivation (
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:
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:
The first few lines simply create and initialize a
StatI instance. (The details of this do not matter here.) All the action is in the last two statements:
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:
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:
checkedCast also returns a null proxy if a facet exists, but the cast is to the wrong type. For example:
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:
This last example also illustrates that
is not the same as
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
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
You can always check what the current facet of a proxy is by calling
This prints the facet name. (For the default facet,
ice_getFacet returns the empty string.)
Facet Exception Semantics
The common exceptions
FacetNotExistException have the following semantics:
This exception is raised only if no facets exist at all for a given object identity.
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
locate instead of simply returning null.