This document provides API-level details for developers to extend SLEE services and Metaswitch Sentinel features.
Introduced in Rhino 2.4.0 |
SBB Parts
This page includes these details for using SBB parts:
What are SBB parts?
An "SBB part", as its name implies, can be thought of as a sub-component of a JAIN SLEE SBB (Service Building Block). An SBB part is an installable SLEE component in its own right. It may have dependencies, and other components may depend on it. The concept of the SBB part component was borne from a use case where the developer wanted to put a number of common shared classes into a library-type component; but the classes had dependencies on various profile specifications, event types, and resource adaptor types — all of which a standard JAIN SLEE library component cannot provide (a library component may only depend on other library components). We thought it might be desirable to extend the definition of the standard library component with the option to declare these other types of references, mainly because a library component was always seen as a passive "primitive" type of component that sat at the top of the dependency hierarchy. Adding dependency links to other component types would significantly complicate the SLEE component dependency hierarchy. In particular, it would create the possibility of cyclic dependencies between different component types — which must be avoided, as in practise they cannot be mapped to a realistic runtime class loader hierarchy. So instead, a new component type was created to fulfill this need: the SBB part.
SBB part components have one other feature that make them distinct from library components: at runtime the SBB part component classes are included in the same class loader as the SBB classes for the service, rather than in a parent class loader (as standard library components are).
SBB part components
An SBB part component may define:
-
dependencies — on libraries, event types, resource adaptor types, profile specifications and other SBB parts
-
per-instance state — held in Container Managed Persistence (CMP) fields that can maintain persistent state that should persist across failures
-
usage statistics — collected through usage parameters interface types declared by the SBB part component (see Usage in the Rhino Administration and Deployment Guide for a description of Rhino’s usage extension mechanism)
-
event handler methods — provided by the SBB part component for each event type it receives, with application logic to process events of a specific event type
-
shareable data — defined by the SBB part component to share with other components as a set of activity context attributes (each activity context attribute has a name and a type, and is stored in one or more activity contexts — an SBB part component defines an activity context interface interface that provides type-safe accessor methods to get and set these attributes).
An SBB part component can only be referenced by SBB components or other SBB part components.
SBBs and SBB parts
An SBB may depend on one or more SBB parts. An SBB part may also depend on other SBB parts. An SBB that depends on one or more SBB parts, both directly and indirectly, has its definition implicitly extended in the following ways:
per-instance state |
The per-instance persistent state of the SBB is defined by the union of the CMP fields declared by the SBB and the CMP fields declared by each dependent SBB part. All CMP fields declared by these components share the same namespace. A CMP field declared by both the SBB and a dependent SBB part, or two different dependent SBB parts, must be declared with the same field type, as they each refer to the same piece of per-instance state. |
---|---|
usage statistics |
The usage parameters interface types available to the SBB is the union of the usage parameters interface types declared by the SBB and the usage parameters interface types declared by each dependent SBB part. All usage parameters interface types declared by these components share the same namespace. A usage parameters interface type declared by both the SBB and a dependent SBB part, or two different dependent SBB parts, must be declared with the same usage parameters interface class name. Amongst the SBB and all dependent SBB parts, at most one usage parameters interface type may be declared as the root usage parameter set type. |
event handler methods |
The event handler methods available to the SBB is the union of the event handler methods defined by the SBB and the event handler methods defined by each dependent SBB part. Any given event type can have at most one event handler method defined between the SBB and the SBB parts — if the SBB declares an event handler method for a given event type, then no dependent SBB part may also declare an event handler method for the same event type. |
shareable data |
The activity context attributes of the SBB are the union of the activity context attributes declared by the SBB and the activity context attributes declared by each dependent SBB part. All activity context attributes declared by these components share the same namespace. An activity context attribute declared by both the SBB and a dependent SBB part, or two different dependent SBB parts, must be declared with the same type, as they each refer to the same attribute. The SBB and dependent SBB parts may each declare an alias for the same activity context attribute, but the alias name must be identical; in other words, each alias declaration must be identical. If any of the components declare an alias for a given activity context attribute, then the alias applies to all the components. |
JNDI environment |
The JNDI bindings available to the SBB is the union of all JNDI bindings declared by the SBB and all JNDI bindings declared by each SBB part. A binding to a given name declared by both the SBB and a dependent SBB part, or two different dependent SBB parts, must be declared with the same value. For example, if the binding is made to a resource adaptor entity, then each binding declaration must have the same resource adaptor type reference. |
Put simply, an SBB becomes the union of the declarations made by itself and all its dependent SBB parts; and there must be no conflict between components that make the same declarations.
SBB part objects
An SBB part may optionally declare an SBB part class. The SBB part class contains the event-processing logic of the SBB part component. An instance of the SBB part class is known as an SBB part object.
The lifecycle of an SBB part object is tightly coupled to the lifecycle of an SBB object. When an SBB object is created, an SBB part object for each dependent SBB part that declares an SBB part class is also created; and the SBB object maintains a reference to this SBB part object for the lifetime of the SBB object. When the SBB object undergoes a lifecycle state transition, for example from the Pooled state to the Ready state, each dependent SBB part object also undergoes the same lifecycle transition. If the SLEE determines that the SBB object is no longer required and becomes eligible for JVM garbage collection, then so too do the dependent SBB part objects.
During the lifetime of an SBB part object, it may be assigned to different SBB entities. When the SBB part object is assigned to an SBB part entity, it can receive events destined for the SBB entity and can manipulate the persistent state of the SBB entity. It can also access the relationships of the SBB entity.
SBB part object lifecycle
An SBB part object can be in one of the following three states:
-
Does Not Exist — The SBB part object does not exist. It may not have been created or it may have been deleted.
-
Pooled — The SBB part object exists but is not assigned to any particular SBB entity.
-
Ready — The SBB part object is assigned to an SBB entity. It is ready to receive events through its event handler methods.
The following steps describe the lifecycle of an SBB part object:
-
An SBB part object’s lifecycle starts when the SLEE creates the object using
newInstance
. The SLEE passes the SBB part object anSbbPartContext
object to the constructor if the constructor defines such an argument. TheSbbPartContext
object allows the SBB part object to invoke functions provided by the SLEE. Once the SBB object is created, the SLEE then injects values into SBB part class fields annotated for dependency injection. -
The SBB part object is bound to an owning SBB object. While the SBB object is in the Pooled state, the SBB part object is also in the Pooled state, and is not associated with any particular SBB entity.
-
An SBB part object transitions from the Pooled state to the Ready state when the SLEE selects the owning SBB object to process an event or to service a logic object invocation. There are two possible transitions from the Pooled state to the Ready state: through the
@PostCreate
method, or through the@OnActivate
method.-
The SLEE invokes the
@PostCreate
method when the SBB part object is assigned to a new SBB entity that has just been created explicitly by an invocation of the create method on aChildRelation
object, or implicitly by the SLEE to process an initial event. -
The SLEE invokes the
@OnActivate
method on an SBB part object when the owning SBB object needs to be activated to receive a method invocation on an existing SBB entity. This occurs when there is no existing SBB object in the Ready state assigned to the SBB entity available to receive the invocation.
-
-
When an SBB part object is in the Ready state, the SBB part object is associated with a specific SBB entity. While the SBB part object is in the Ready state, the SLEE can synchronise the transient state held in the SBB part object with the persistent state of the SBB entity whenever it determines the need to, by invoking the
@PostLoad
and@PreStore
methods zero or more times. Event handler and exception callback methods can be invoked on the SBB part object zero or more times. Invocations of the@PostLoad
and@PreStore
methods can be arbitrarily mixed with invocations of these methods subject to the SBB part object lifecycle. -
The SLEE can choose to passivate an SBB object. Passivating an SBB object disassociates the SBB object from the SBB entity it is currently assigned to. When an SBB object is passivated, its dependent SBB part objects are also passivated and therefore disassociated from the SBB entity. When an SBB part object is passivated, the SLEE first invokes the
@PreStore
method to allow the SBB part object to prepare itself for the synchronisation of the SBB entity’s persistent state with the SBB part object’s transient state; then the SLEE invokes the@OnPassivate
method to return the SBB part object to the Pooled state. -
Eventually, the SLEE will transition the SBB part object to the Pooled state. There are two possible normal transitions from the Ready state to the Pooled state: through the
@OnPassivate
method, and through the@PreRemove
method.-
The SLEE invokes the
@OnPassivate
method when the SLEE wants to disassociate the SBB object and its dependent SBB part objects from the SBB entity without removing the SBB entity. -
The SLEE invokes the
@PreRemove
method when the SLEE wants to remove the SBB entity (in other words, when the SBB entity is removed as part of cascading removal of an SBB entity sub-tree).
-
-
When the SBB object and its dependent SBB part objects are put back into the pool, they are no longer associated with the SBB entity. The SLEE can assign the SBB object and SBB part objects to any SBB entity of the same SBB component.
-
The SLEE may release its references to an SBB object in the pool, along with its dependent SBB part objects, allowing them to be garbage collected. It may do this after calling the
unsetSbbContext
method on the SBB object, and the@PreDispose
method on each SBB part object.
|
SBB part class
An SBB part must define an SBB part class if the SBB part declares event handler methods. In any other case, the definition of an SBB part class is optional. An SBB part class must:
-
be defined in a named package; that is, the class must have a package declaration
-
be defined as public
-
not be abstract or final
-
not define the finalize method.
The SBB developer implements the SBB part class. In it, they may define lifecycle and event handler methods, and an exception callback. The SBB part class may also use Dependency Injection for various types of fields.
Lifecycle methods
Each SBB part object has a lifecycle. The SLEE invokes the lifecycle methods of the SBB part object to make the SBB part object aware of its lifecycle state. The SLEE invokes a given lifecycle method on an SBB part object at the same time that it invokes the corresponding lifecycle method on the owning SBB object; however the order in which different dependent SBB parts are invoked for a given lifecycle method invocation is not defined.
The lifecycle methods are described below. In the case of lifecycle methods denoted by annotations, at most one of each lifecycle method may be declared in an SBB part class.
Constructor
The SBB part class must define a public constructor that takes either no arguments or a single argument of type com.opencloud.rhino.slee.sbbpart.SbbPartContext. The SLEE invokes the constructor of an SBB part class to create a new SBB part object. The SLEE creates a new SBB part object when the owning SBB object transitions from the Does Not Exist state to the Pooled state. If the SBB part object needs to use the SbbPartContext object during its lifetime, it should keep a reference to the SbbPartContext object in an instance variable. Alternatively, the SBB part object may obtain a reference to an SbbPartContext object using Dependency Injection.
During the constructor invocation, the SBB part object is not assigned an SBB entity. The SBB part object can use the constructor to allocate and initialise state or connect to resources that are to be held by the SBB part object during its lifetime. Such state and resources cannot be specific to an SBB entity, because the SBB part object might be reused during its lifetime to service multiple SBB entities.
The SBB part object constructor invocation corresponds to the setSbbContext
lifecycle method of the SBB abstract class, and is invoked after the setSbbContext
method returns successfully.
If both supported constructors are defined, then the one-argument constructor is used.
|
@PreDispose
The SBB part class may optionally implement a lifecycle callback method invoked by the SLEE before the SLEE terminates the life of the SBB part object. This method must be annotated with the @PreDispose
annotation. This method is invoked when the SBB part object transitions from the Pooled state to the Does Not Exist state. During this method, an SBB entity is not assigned to the SBB part object. The SBB part object can use this method to free state or resources that are held by the SBB part object. These state and resources typically had been allocated by the SBB part class constructor.
This method corresponds to the unsetSbbContext
lifecycle method of the SBB abstract class, and is invoked before the unsetSbbContext
method is invoked on the owning SBB object.
|
@PostCreate
The SBB part class may optionally implement a lifecycle callback method invoked when a new SBB entity is created. The method must be annotated with the @PostCreate
annotation. This method is invoked when the SBB part object transitions from the Pooled state to the Ready state as a result of SBB entity creation, and is invoked after the persistent representation of the SBB entity has been created and the SBB part object is assigned to the created SBB entity. This method can be used to initialise any transient state and acquire any resources that the SBB part needs while it is in the Ready state.
|
This method corresponds to the sbbPostCreate
lifecycle method of the SBB abstract class, and is invoked after the sbbPostCreate
method returns successfully.
Note that there is no SBB part lifecycle method equivalent to the SBB abstract class sbbCreate
method.
@OnActivate
The SBB part class may optionally implement a lifecycle callback method invoked when the SLEE needs to assign an SBB part object in the Pooled state to a existing SBB entity. This method must be annotated with the @OnActivate
annotation. The SBB part object transitions to the Ready state after this method returns. This method gives the SBB part object a chance to initialise additional transient state and acquire additional resources that it needs while it is in the Ready state.
|
This method corresponds to the sbbActivate
lifecycle method of the SBB abstract class, and is invoked after the sbbActivate
method returns successfully.
@OnPassivate
The SBB part class may optionally implement a lifecycle callback method invoked when the SLEE decides to disassociate an SBB part object in the Ready state from the SBB entity it is currently associated with. This method must be annotated with the @OnPassivate
annotation. The SBB part object transitions to the Pooled state after this method returns. This method gives the SBB part object the change to release any state or resources that should not be held while the SBB part object is in the Pooled state. These state and resources typically had been allocated during the @OnActivate
method.
|
This method corresponds to the sbbPassivate
lifecycle method of the SBB abstract class, and is invoked after the sbbPassivate
method returns successfully.
@PreRemove
The SBB part class may optionally implement a lifecycle callback method invoked by the SLEE when the SBB entity assigned to the SBB part object is about to be removed. This method must be annotated with the @PreRemove
annotation. The SBB part object is in the Ready state when this method is invoked, and it will transition to the Pooled state after this method returns. This method can be used to implement any actions that must be done before the SBB entity’s persistent representation is removed.
|
This method corresponds to the sbbRemove
lifecycle method of the SBB abstract class, and is invoked before the sbbRemove
method is invoked on this owning SBB object.
@PostLoad
The SBB part class may optionally implement a lifecycle callback method invoked by the SLEE to synchronise the state of the SBB part object with its assigned SBB entity’s persistent state. This method must be annotated with the @PostLoad
annotation. The SBB developer can assume that the persistent state of the SBB entity the SBB part object is assigned to has been loaded just before this method is invoked. It is the responsibility of the SBB developer to use this method to re-compute or initialise the values of any transient instance variables in the SBB part object that depend on the SBB entity’s persistent state. In general, any transient state that depends on the persistent state of an SBB entity should be recalculated in this method. The SBB developer can use this method, for instance, to perform some computation on the values returned by the CMP field accessor methods, such as converting text fields to more convenient objects or binary representations.
|
This method corresponds to the sbbLoad
lifecycle method of the SBB abstract class, and is invoked after the sbbLoad
method returns successfully.
@PreStore
The SBB part class may optionally implement a lifecycle callback method invoked by the SLEE to synchronise the SBB entity’s persistent state with the state of the SBB part object. This method must be annotated with the @PreStore
annotation. The SBB developer should use this method to update the SBB entity using the CMP field accessor methods before its persistent state is synchronised. For example, this method may perform conversion of object or binary data representations to text. The SBB developer can assume that after this method returns, the persistent state is synchronised.
|
This method corresponds to the sbbStore
lifecycle method of the SBB abstract class, and is invoked after the sbbStore
method returns successfully.
Event handler methods
An SBB part class may receive an event through one of its event handler methods. An SBB part declares event handler methods in the same way as an SBB. For each event type received by the SBB part, you must:
-
provide an event element in the SBB part’s
sbb-part
deployment descriptor element — the value of theevent-direction
attribute of the event element must beReceive
. It must also include anevent-name
element and anevent-type-ref
element.-
The
event-name
element provides the SBB part scoped name used within the SBB part class to identify the event type, and determines the name of the event handler method. -
The
event-type-ref
element references anevent-definition
element that provides the event type and the event class. -
The
event-definition
element is provided and defined by the event producer of the event type. -
The
initial-event
attribute of the event element may optionally be set toTrue
. -
The event element may optionally include an
event-resource-option
element.
-
-
implement the event handler method in the SBB part class — this method contains the application logic that will be invoked to process events of this event type.
The name of the event handler method is derived from the event name of the event type that will be received by the event handler method. The method name of the event handler method is derived by adding an on
prefix to the event name. The event handler method has one of the following method signatures:
public void on<event name>(<event class> event,
<SBB Part Activity Context Interface interface> activity);
public void on<event name>(<event class> event,
<SBB Part Activity Context Interface interface> activity,
EventContext eventContext);
|
Event handler methods declared by SBB parts have the same rules and restrictions as event handler methods declared by SBBs.
An SBB part can manage the event types that it may receive for a particular activity context which it is attached to, by altering the event mask as an SBB would. The maskEvent
and getEventMask
methods defined in the SbbPartContext
interface behave identically to the same methods defined in the SbbContext
interface. An individual SBB or SBB part may only mask the events that it receives. For example, an SBB cannot mask an event received by a dependent SBB part.
Initial event selector methods
An SBB part class may define an initial event selector method for any event declared as an initial event. The behaviour and function of an initial event selector declared by an SBB part is identical to the behaviour and function of an initial event selector method declared by an SBB. The method signature of the initial event selector method is as follows:
public InitialEventSelector <initial event selector method name>(InitialEventSelector ies);
|
Exception callback method
The SBB part class may optionally implement a callback method invoked by the SLEE to handle RuntimeExceptions
thrown by the SBB part’s event handler methods and the mandatory transactional lifecycle callback methods: @PostCreate
, @PreRemove
, @PostLoad
, and @PreStore
. This method must be annotated with the @OnException
annotation.
|
A well-written SBB part should not throw any RuntimeExceptions from any of its SLEE invoked methods. Instead, the SBB part should place the exception handling logic inside a try { } catch (Throwable) clause and handle RuntimeExceptions within each invoked method. |
RuntimeException handling for transactional methods
When a SLEE originated mandatory transactional method is invoked on an SBB part object and the invocation returns with a RuntimeException
thrown, the SLEE performs the following actions:
-
The SLEE logs this condition.
-
The SLEE marks the transaction of the invocation for rollback.
-
The SLEE invokes the
@OnException
method, if declared, of the same SBB part object with the same transaction. The SBB part object may be in the Pooled state or in the Ready state. For example, if a@PostCreate
method throws theRuntimeException
, then the SBB part object remains in the Pooled state when the SLEE invokes the@OnException
method on the SBB part object. If an event handler method throws theRuntimeException
, then the SBB part object remains in the Ready state when the SLEE invokes the@OnException
method on the SBB part object. -
The SLEE moves the SBB part object, along with the owning SBB object and any other dependent SBB part objects, to the Does Not Exist state.
-
If the
@OnException
method of the SBB part object returns with anotherRuntimeException
thrown, the SLEE logs this condition. The@OnException
method is not reinvoked in this case.
RuntimeException handling for non-transactional methods
When the SLEE invokes a non-transactional method of an SBB part object and the invocation returns by throwing a RuntimeException
, the SLEE performs following sequence of actions:
-
The SLEE logs this occurrence.
-
The SLEE moves the SBB part object, along with the owning SBB object any other dependent SBB part objects, to the Does Not Exist state.
Transaction rollback processing
An SBB part object is not involved with transaction rollback processing. If transaction rollback occurs after the SLEE invokes a mandatory transactional method on an SBB part object, such as an event handler method, the sbbRolledBack
callback method will be invoked on an SBB object of the SBB part’s owning SBB in accordance with the normal SLEE rules for rollback processing.
Method name restrictions
Non-private (such as public, protected, or package private) methods that are defined in an SBB part class must not begin with sbbPart
.
Dependency injection
When implementing an SBB part class, the SBB developer has the option to use dependency injection to initialise the value of certain types of class fields. Dependency injection eliminates the need for the typical boilerplate code associated with the initialisation of these fields.
Dependency injection is supported using the API provided by JSR 330 Dependency Injection for Java, in particular the @javax.inject.Inject
and @javax.inject.Named
annotations. Any SBB part class field where dependency injection is required must be annotated with @Inject
. The @Named
annotation may also be used, where permitted, to provide an additional parameter to the injector.
The @Inject
annotation may be used on SBB part class fields of the following types:
-
com.opencloud.rhino.slee.sbbpart.SbbPartContext
-
com.opencloud.rhino.cmp.CMPFields
, and any type that can be assigned any CMP Extension Interface defined by the SBB part -
javax.slee.facilities.Tracer
orcom.opencloud.rhino.facilities.Tracer
-
The
@Named
annotation may be used to specify the name of the tracer to assign to the field. The named value must be a valid SLEE tracer name.
-
-
javax.slee.facilities.ActivityContextNamingFacility
-
javax.slee.facilities.AlarmFacility
-
javax.slee.facilities.TimerFacility
-
javax.slee.profile.ProfileFacility
orcom.opencloud.rhino.facilities.profile.ProfileFacility
-
com.opencloud.rhino.facilities.childrelations.ChildRelationFacility
-
com.opencloud.rhino.facilities.usage.UsageFacility
-
The
UsageFacility
is only available to SBB parts that declare at least one usage parameters interface. -
If the SBB part declares a root usage parameter set type, then any type that can be assigned the usage parameters interface of the root usage parameter set type.
-
This field will be assigned the root usage parameter set for the SBB part.
-
-
com.opencloud.rhino.license.LicenseFacility
-
javax.slee.profile.ProfileTableActivityContextInterfaceFactory
-
javax.slee.nullactivity.NullActivityFactory
-
The
@Named
annotation may be used to indicate the specific type of null activity factory to assign to the field. The named value must be one ofreplicated
,non-replicated
, or the empty string@Named
annotation.
-
-
javax.slee.nullactivity.NullActivityContextInterfaceFactory
-
javax.slee.serviceactivity.ServiceActivityFactory
-
javax.slee.serviceactivity.ServiceActivityContextInterfaceFactory
An injected field must not be static or final. Any access modifier (public, protected, package private, or private) is permitted.
If an SBB part class makes use of dependency injection, the SLEE injects these references after the SBB part object is created; in other words, after the constructor invocation has returned, and before any other methods are invoked on the object. Fields are injected beginning with the topmost superclass that requests injection, then working down through each subclass as required.
SBB abstract class abstract method replacements
The SBB abstract class allows the declaration of abstract methods in order to provide various functionality to the SBB code. Since the SBB part class cannot be abstract, alternative mechanisms are provided so that the same functionality is available to SBB parts. These mechanisms are described below.
Per-instance state
An SBB declares its per-instance state by defining abstract getter and setter methods in the SBB abstract class and indicating that they relate to CMP fields using <cmp-field>
entries in the SBB deployment descriptor. Rhino also allows an SBB to declare CMP fields using CMP extension interfaces. An SBB part can also define its per-instance state using CMP extension interfaces. The SBB part obtains access to the CMP fields defined in CMP extension interfaces using a com.opencloud.rhino.cmp.CMPFields
object, obtainable from its SbbPartContext
object. The CMPFields
object may be typecast to any CMP extension interface declared by the SBB part, thus exposing the CMP field accessor methods defined by the interface.
Activity context interface narrow method
An SBB that declares an activity context interface that is a subtype of javax.slee.ActivityContextInterface
is expected to define an abstract activity context interface narrow method in the SBB abstract class. This method converts or "narrows" a generic javax.slee.ActivityContextInterface
object to an object implementing the SBB’s activity context interface. An SBB part that declares an activity context interface that is a subtype of javax.slee.ActivityContextInterface
can similarly narrow a generic javax.slee.ActivityContextInterface
object using the asSbbPartActivityContextInterface
method on its SbbPartContext
object. This method returns an activity context interface object that implements the SBB part’s declared activity context interface.
Child relations
For each child relation that an SBB has, an abstract child relation accessor method must be declared in the SBB abstract class. This method returns a javax.slee.ChildRelation
object that allows child SBBs to be created, inspected, and removed.
As SBB parts cannot declare their own SBB child relations, SBB parts do not need to define child relation accessor methods.
Profile CMP interface accessor method
The profile CMP interface accessor method was deprecated in the JAIN SLEE 1.1 specification. As a replacement, SBBs can use the profile facility and ProfileTable
objects to query and access profiles.
There is no equivalent to the profile CMP interface accessor method for SBB parts. Like SBBs, SBB parts can use the profile facility to query and access profiles.
Usage parameters interface accessor methods
An SBB that declares a usage parameters interface is expected to declare at least one abstract usage parameters interface accessor method in the SBB abstract class. This method returns an object implementing the SBB’s usage parameters interface allowing usage statistics for the SBB to be accumulated.
Rhino provides an extension mechanism that allows an SBB to declare more than one usage parameters interface, and defines a usage facility with which SBBs can manage and access their usage parameter sets. the usage facility eliminates the need for an SBB to declare the usage parameters interface accessor methods. An SBB part may also declare usage parameters interfaces using the same extension mechanism, and also may manage and access the SBB’s usage parameter sets using the usage facility.
SbbPartContext interface
The SLEE provides each SBB part object, if requested through an SBB part class constructor argument or dependency injection, with an SbbPartContext
object. The SbbPartContext
object gives the SBB part object access to the SBB part object’s context maintained by the SLEE, allows the SBB part object to invoke functions provided by the SLEE, and obtains information about the SBB entity assigned to the SBB part object.
An SbbPartContext
object is associated with one service and one SBB; and the associated service and SBB do not change during the lifetime of that SbbPartContext
object.
The SbbPartContext
object implements the SbbPartContext
interface. The SbbPartContext
interface extends the JAIN SLEE defined SbbContext
interface with additional functionality, as described below.
Methods inherited from SbbContext
The methods inherited from the JAIN SLEE defined SbbContext
interface have the same meaning and purpose when used by SBB parts.
SbbPartContext interface getSbbPart method
The getSbbPart
method returns an SbbPartID
object that encapsulates the component identity of the SBB part.
SbbPartContext interface getTracer method
The getTracer
method overrides the same method from SbbContext
to return a Rhino-specific extension of the Tracer
interface.
For more about this Tracer extension, please see SLEE Facilities. |
SbbPartContext interface asSbbPartActivityContextInterface method
The asSbbPartActivityContextInterface
method is used by the SBB part to narrow an object that implements the generic ActivityContextInterface
to an object that implements the SBB part activity context interface so that the SBB part can access the activity context interface attributes defined in the SBB part activity context interface.
This method takes as its input parameter an activity context interface object and returns an object that implements the SBB part activity context interface interface of the SBB part. The SBB part activity context interface interface provides the accessor methods that allow an SBB part object to access the shareable attributes of the SBB part that are stored in the activity context interface.
If the SBB part does not define an SBB part activity context interface interface, then this method returns the same object passed in as a parameter.
SbbPartContext interface getActivities methods
The SbbPartContext
interface defines two getActivities
methods:
-
getActivities()
— This method overrides the same method fromSbbContext
to return a Rhino-specific extension of theActivityContextInterface
interface. Otherwise, this method behaves in the same way as defined by the JAIN SLEE specification for SBBs.
For more about this ActivityContextInterface extension, please see Miscellaneous SLEE API Enhancements. |
-
getActivities(Class)
— This method behaves similarly to the no-argument version; however it only returns activity context interface objects where the type of the underlying activity object is assignable to the class argument. For example, if this method was invoked withNullActivity.class
as an argument, then only activity context interface objects for the null activities the SBB entity currently associated with the SBB part is attached to would be returned.
SbbPartContext interface getConvergenceName method
The getConvergenceName
method returns the convergence name that the SBB entity the SBB part is associated with was created with. The value returned from this method is a vendor-specific string that uniquely identifies the initial event selector conditions that led to the SBB entity’s creation.
This method only returns a non-null
value if invoked on an SbbPartContext
object belonging to a root SBB entity.
SbbPartContext interface getCMPFields method
The getCMPFields
method provides the SBB part with access to its per-instance state.
SbbPartContext interface getJndiBindings method
The getJndiBindings
method returns a map describing the JNDI bindings available to the SBB part.
For more about this method, please see SLEE Facilities. |
SbbPartContext interface enableEntityTreePersistence method
The enableEntityTreePersistence
method enables persistence of application state to external replicated storage resources such as a key/value store. Initial replicated persistence of application state can be disabled using the service-properties
element in the extension service deployment descriptor, then enabled using this method when the SBB entity has reached a stable state.
For more about application initiated persistence, please see Application initiated persistence. |
SbbPartContext interface getConvergenceNameSessionOwnershipRecord methods
These methods obtain a reference to the convergence name session ownership record for the SBB entity tree.
For more about convergence name session ownership records, please see Convergence name session ownership record. |
SBB part component environment
An SBB part has access to the same JNDI environment bindings as its owning SBB. All the SLEE facilities, environment entries, and resource adaptor type bindings that are available to an SBB are also available to all its dependent SBB parts. An SBB part accesses its JNDI environment in exactly the same way as an SBB.
The current specification of SBB parts does not yet allow an SBB part component to declare its own environment entries. An SBB part component may however define its own resource adaptor type bindings.
SBB part example
Below is an example of an SBB part class. The SBB part declares an event handler method that receives a SLEE timer event, which logs the event and increments a CMP field and a usage counter:
import javax.inject.Inject;
import javax.inject.Named;
import javax.slee.CreateException;
import javax.slee.facilities.TimerEvent;
import com.opencloud.rhino.facilities.Tracer;
import com.opencloud.rhino.slee.lifecycle.PostCreate;
public class ExampleSbbPart {
@PostCreate
public void onCreate() throws CreateException {
rootTracer.info("SBB part created");
}
public void onTimerEvent(TimerEvent event, ExampleSbbPartActivityContextInterface aci) {
// get CMP counter
int count = cmpFields.getCounter() + 1;
// log event
timerTracer.info("received timer event: " + count);
// increment CMP counter
cmpFields.setCounter(count);
// record usage stats
rootUsage.incrementTimerEvents(1);
...
}
@Inject
private Tracer rootTracer;
@Inject @Named("timer")
private Tracer timerTracer;
@Inject
private ExampleSbbPartCMPInterface cmpFields;
@Inject
private ExampleSbbPartUsageInterface rootUsage;
}
CMP Field Enhancements
This page provides an API for the following CMP field enhancements:
Array support
As an extension to the JAIN SLEE specification, Rhino supports CMP field declarations of arrays for the following SLEE-defined types:
-
javax.slee.ActivityContextInterface
, and any subclass of this interface -
javax.slee.SbbLocalObject
, and any subclass of this interface-
If the abstract getter and setter methods for the CMP field are defined in the SBB abstract class (as opposed to a CMP extension interface), the corresponding
<cmp-field>
declaration in the deployment descriptor may not include an<sbb-alias-ref>
element.
-
-
javax.slee.EventContext
-
javax.slee.profile.ProfileLocalObject
, and any subclass of this interface.
Arrays may be declared with any dimension. Array support is automatic wherever the basic type is supported in CMP fields; in other words, no user prompt or directive is necessary.
Serialization enhancements
Rhino introduces a number of enhancements that offer significantly more control and flexibility over how CMP field values are serialized, and much better serialization performance, when compared with standard Java serialization. These include:
FastSerializable
The com.opencloud.util.FastSerializable
interface provides a simple alternative to the standard Java java.io.Serializable
interface where the exact type of an object to be serialized is known at compile time.
The FastSerializable
interface is defined as follows:
package com.opencloud.util;
public interface FastSerializable {
public void toStream(java.io.DataOutput stream)
throws java.io.IOException;
}
A class implementing the FastSerializable
interface must provide a public constructor that takes either:
-
a single
java.io.DataInput
argument; or -
a
java.io.DataInput
argument and ajava.lang.ClassLoader
argument.
If both constructors are declared by a given class then the two-argument constructor is used.
Serialization of the object is performed by an invocation of the toStream
method. Object state must be written to the DataOutput
passed as the input argument. Deserialization is performed by new object instantiation and constructor invocation. Object state can be read from the DataInput
argument. The ClassLoader
argument, if present, can be used to resolve any application-specific classes stored in the stream.
Rhino recognises and supports the FastSerializable
contract on all CMP fields. Arrays of FastSerializable
types of any dimension are handled automatically by Rhino, and no special treatment is necessary.
While the FastSerializable
contract has some similarity to the java.io.Externalizable
contract, there are some differences that warrant discussion:
-
FastSerializable
operates in terms of data I/O streams, whileExternalizable
operates in terms of object I/O streams. The reason for this is thatFastSerializable
desires to avoid the generally costly serialization overhead that occurs when serializing arbitrary objects. By limiting the stream I/O to basic datatypes, it forces the user to think about the most performance-efficient manner in which the object state can be serialized and deserialized. -
When
FastSerializable
types are used in CMP fields, Rhino’s CMP implementation typically determines the type of object to reconstruct during deserialization at code-generation time, not run time; so care must be taken that objects are restored using the correct type. Problems occur, for example, if a CMP field is declared in terms ofFastSerializable
typeFoo
, but an object of subclassBar
is stored in the CMP field. The CMP implementation assumes that the CMP field will only store aFoo
object, and will instantiate aFoo
object when the CMP field is deserialized, leading to deserialization failures. Making classes that implementFastSerializable
final is good practice, unless this issue is taken into consideration. -
No handling of shared references is performed by the implementation. If the same object is encountered twice during serialization, then two copies of the object will be stored and subsequently deserialized.
FastSerialize
The com.opencloud.util.FastSerialize
class provides some utility functions that may be useful to application developers implementing their own serialization logic based around FastSerializable
.
Example
Below is an example of a FastSerializable
type:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import com.opencloud.util.FastSerializable;
public final class Person implements FastSerializable {
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public Person(DataInput in, ClassLoader classLoader) throws IOException {
firstName = in.readUTF();
lastName = in.readUTF();
age = in.readInt();
}
@Override
public void toStream(DataOutput out) throws IOException {
out.writeUTF(firstName);
out.writeUTF(lastName);
out.writeInt(age);
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
private final String firstName;
private final String lastName;
private final int age;
}
Encodable
The com.opencloud.rhino.cmp.Encodable
interface provides a similar contract to that of FastSerializable
but is specifically targeted at types stored in SBB CMP fields. It provides access to utility functions to encode and decode SLEE-specific datatypes that are not defined as serializable, such as SBB and profile local object references, and an encodable context that can be set by an SBB to pass information to the encoding . decoding methods, such as references to resource adaptor provider objects.
The Encodable
interface is defined as follows:
package com.opencloud.rhino.cmp;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
public interface Encodable {
public void encode(java.io.DataOutput out, EncoderUtils utils)
throws java.io.IOException;
}
A class implementing the Encodable
interface must provide a public constructor that takes, in this order: a java.io.DataInput
argument, a java.lang.ClassLoader
argument, and a com.opencloud.rhino.cmp.codecs.DecoderUtils
argument.
Serialization of the object is performed by an invocation of the encode
method. Object state must be written to the DataOutput
passed as the input argument. Deserialization is performed by new object instantiation and constructor invocation. Object state can be read from the DataInput
argument. The ClassLoader
argument can be used to resolve any application-specific classes stored in the stream.
The constructor and encode
methods of an Encodable
type are always invoked with the same transaction context used to access or update the CMP field. This is typically only of consequence if an encodable context is used to provide access to other SBB CMP fields.
EncoderUtils / DecoderUtils
SLEE-defined datatypes such as SBB and profile local objects, activity context interface objects, and event context objects are not defined by the SLEE specification as being serializable. The SLEE specification does provide provision for storing objects of these types directly into CMP fields; but the lack of implicit serializability means that, for example, an SBB local object reference cannot be encapsulated within some other object which is stored into CMP, as object serialization will fail when it reaches the unserializable SBB local object reference.
The EncoderUtils
object passed to the encode
method provides access to methods that can serialize these SLEE-defined datatypes, allowing classes that implement the Encodable
contract to encapsulate objects of these datatypes and still be storable into CMP fields. The corresponding DecoderUtils
object passed to the decoding constructor provides access to methods that can deserialize these datatypes, allowing correct object reconstruction during deserialization.
Rhino recognises and supports the Encodable
contract on all CMP fields; however the utility methods provided by SleeDatatypeEncoder
and SleeDatatypeDecoder
only function under certain conditions:
-
Encode and decode of all SLEE-defined datatypes is supported for SBB and SBB Part CMP fields.
-
Encode and decode of
EventContext
objects is supported for Activity Context Interface attributes. -
Encode and decode of SLEE-defined datatypes is unsupported in any other case, and invoked methods will throw a
java.lang.UnsupportedOperationException
.
Arrays of Encodable
types of any dimension are handled automatically by Rhino, and no special treatment is necessary.
Unlike FastSerializable
types, CMP fields that store Encodable
types may, at runtime, store a subclass of the declared CMP field type without issue. For example, if a CMP field is declared in terms of Encodable
type Foo
, an object of subclass Bar
may be stored in the CMP field and it will serialize and deserialize as expected. Deserialization is, however, more efficient if the type of the stored object is the same as the CMP field type, as reflection must be used to reconstruct a stored object if the type of the stored object differs from the expected type.
Like FastSerializable
, no handling of shared references is performed by the implementation. If the same object is encountered twice during serialization, then two copies of the object will be stored and subsequently deserialized.
Example
Below is an example of an Encodable
type:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import javax.slee.ActivityContextInterface;
import com.opencloud.rhino.cmp.Encodable;
import com.opencloud.rhino.cmp.codecs.DecoderUtils;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
import com.opencloud.rhino.cmp.codecs.SleeDatatypeDecoder;
import com.opencloud.rhino.cmp.codecs.SleeDatatypeEncoder;
public final class Relay implements Encodable {
public Relay(IncomingSbbLocalObject incoming, OutgoingSbbLocalObject outgoing, ActivityContextInterface aci) {
this.incoming = incoming;
this.outgoing = outgoing;
this.aci = aci;
}
public Relay(DataInput in, ClassLoader classLoader, DecoderUtils utils) throws IOException {
SleeDatatypeDecoder decoder = utils.getSleeDatatypeDecoder();
incoming = decoder.decodeSbbLocalObject(in);
outgoing = decoder.decodeSbbLocalObject(in);
aci = decoder.decodeActivityContextInterface(in);
messageCount = in.readInt();
}
@Override
public void encode(DataOutput out, EncoderUtils utils) throws IOException {
SleeDatatypeEncoder encoder = utils.getSleeDatatypeEncoder();
encoder.encodeSbbLocalObject(incoming, out);
encoder.encodeSbbLocalObject(outgoing, out);
encoder.encodeActivityContextInterface(aci, out);
out.writeInt(messageCount);
}
public IncomingSbbLocalObject getIncomingSbb() { return incoming; }
public OutgoingSbbLocalObject getOutgoingSbb() { return outgoing; }
public ActivityContextInterface getActivityContextInterface() { return aci; }
public void incMessageCount() { messageCount++; }
public int getMessageCount() { return messageCount; }
private final IncomingSbbLocalObject incoming;
private final OutgoingSbbLocalObject outgoing;
private final ActivityContextInterface aci;
private int messageCount;
}
Datatype codecs
The Encodable
contract provides an in-line mechanism for object serialization. That is, code for serialization and deserialization forms part of the class itself. There may be times, however, when it is desired or necessary for the serialization code to be separated from the class being serialized. For example, the serialization logic may have common components that can be shared between multiple classes, or the source code for the class being serialized may not be available to be enhanced to support the FastSerializable or Encodable serialization contracts.
To support these situations, Rhino allows a datatype codec to be defined and associated with either a CMP field or a serializable class directly. The datatype codec specifies how objects of the target type are serialized and deserialized, essentially providing a third-person perspective to the Encodable
contract.
A datatype codec must implement the com.opencloud.rhino.cmp.codecs.DatatypeCodec
interface. This interface is defined as follows:
package com.opencloud.rhino.cmp.codecs;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public interface DatatypeCodec<T> {
public void encode(T value, DataOutput out, EncoderUtils utils)
throws IOException;
public T decode(DataInput in, ClassLoader classLoader, DecoderUtils utils)
throws IOException;
}
A datatype codec class must be public (and static, if an inner class) and implement a public no-argument constructor. The encode
method functions identically to the encode
method defined by the Encodable
interface, but takes the object to be serialized as an additional argument. The decode
method functions identically to the deserialization constructor required by the Encodable
contract, but returns the deserialized object as the method return result. The generic type parameter T
identifies the type of object that the datatype codec is capable of serializing and deserializing. Generally, a datatype codec does not need to concern itself with encoding or decoding null
values, as Rhino will only invoke the codec for non-null
values. There is one exception to this rule, discussed in the Datatype codecs for collections section below.
The encode
and decode
methods are always invoked with the same transaction context used to access or update the CMP field. This is typically only of consequence if an encodable context is used to provide access to other SBB CMP fields.
A datatype codec is associated with the corresponding datatype using the @DatatypeCodecType
annotation. This annotation requires the datatype codec class to be specified as an argument. The annotation can be used either directly on the target class to be serialized, or attached to a CMP field getter or setter method. If attached to a CMP field getter or setter method of an array type, then the datatype codec need only be defined in terms of the base array component type. The codec will be invoked for each non-null
array element encountered during serialization or deserialization.
Datatype codecs are supported on all CMP fields where Encodable
types are supported, and have the same conditions of use.
The @DatatypeCodecType
annotation may not be used on a CMP field getter or setter method where the CMP field is one of the following types, or is an array of any dimension of one of the following types:
-
javax.slee.ActivityContextInterface
, and any subclass of this interface -
javax.slee.SbbLocalObject
, and any subclass of this interface -
javax.slee.EventContext
-
javax.slee.profile.ProfileLocalObject
, and any subclass of this interface.
Examples
Below is an example of a datatype codec handling the serialization of class type Customer
:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import com.opencloud.rhino.cmp.codecs.DatatypeCodec;
import com.opencloud.rhino.cmp.codecs.DecoderUtils;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
public class CustomerCodec implements DatatypeCodec<Customer> {
@Override
public void encode(Customer value, DataOutput out, EncoderUtils utils) throws IOException {
...
}
@Override
public Customer decode(DataInput in, ClassLoader classLoader, DecoderUtils utils) throws IOException {
return ...
}
}
The datatype codec can be associated directly with the Customer
datatype by annotating the class itself as shown below:
@DatatypeCodecType(CustomerCodec.class)
public class Customer ...
Alternatively, a CMP field storing a Customer
object can be annotated to declare the datatype codec to be used for that CMP field, as shown below:
public abstract class MySbb implements Sbb {
...
@DatatypeCodecType(CustomerCodec.class)
public abstract void setCustomer(Customer customer);
public abstract Customer getCustomer();
}
The datatype codec can be equally used on a CMP field storing an array of Customer
objects, as shown below:
public abstract class MySbb implements Sbb {
...
@DatatypeCodecType(CustomerCodec.class)
public abstract void setCustomers(Customer[] customers);
public abstract Customer[] getCustomers();
}
Encodable context
There are a number of use cases where serialization and/or deserialization of a CMP object requires access to resources that the Encodable and DatatypeCodec encode and decode methods may not natively have access to. For example, deserialization of a stored network message may require access to the owning resource adaptor provider object to reconstruct the correct object graph. To solve this problem, Rhino defines the concept of an encodable context. An encodable context can provide access to the resources that the encode and decode methods need in order to fulfill their function.
Encodable context definition
The encodable context required for any given Encodable
or DatatypeCodec
type may be defined as either a class or interface, though it is strongly recommended that the context be defined as an interface. The reason for this is to allow the provider of the context to more easily combine contexts required by multiple Encodable
or DatatypeCodec
types used in different CMP fields into a single implementation object. An encodable context may provide read access to other CMP states, but should not provide write access to any CMP state. SLEE behaviour is undefined if arbitrary CMP fields are modified during the encode or decode of another CMP field. Otherwise, there are no specific requirements or restrictions about what an encodable context may provide access to.
Below is an example of an encodable context interface:
import org.jainslee.resources.diameter.ro.RoProviderFactory;
public interface RoProviderContext {
public RoProviderFactory getRoProviderFactory();
public Tracer getTracer();
public int getSomeContextValue();
}
An Encodable
or DatatypeCodec
object obtains an encodable context object from the EncoderUtils
argument passed to the encode
method, or from the DecoderUtils
argument passed to the deserialization constructor (for Encodables
) or decode
method (for DatatypeCodecs
).
Encodable context provider
If an Encodable
or DatatypeCodec
type is a consumer of an encodable context, then there must be a corresponding provider of the context. Only SBBs currently support providing an encodable context object for use by CMP fields declared by itself or any dependent SBB part. An SBB sets the encodable context using the setEncodableContext
method on its com.opencloud.rhino.slee.RhinoSbbContext
object (a Rhino extension of javax.slee.SbbContext
):
package com.opencloud.rhino.slee;
public interface RhinoSbbContext extends SbbContext {
public <T> void setEncodableContext(T context)
throws SLEEException;
...
}
The encodable context object set by the SBB must implement all encodable context types expected by the Encodable
or DatatypeCodec
types that it uses, so that each Encodable
or DatatypeCodec
type may typecast the object to the encodable context type that it expects. An encodable context object is scoped to an SBB object; therefore the recommended place to initialise the encodable context is in the SBB’s setSbbContext
method.
Below is an example implementation of the RoProviderContext
encodable context interface shown above:
import javax.naming.InitialContext;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import com.opencloud.rhino.slee.RhinoSbbContext;
import org.jainslee.resources.diameter.ro.RoProviderFactory;
public abstract class MySbb implements Sbb {
public void setSbbContext(SbbContext context) {
RhinoSbbContext rhinoContext = (RhinoSbbContext)context;
final RoProviderFactory roProviderFactory = new InitialContext().lookup(...);
final Tracer encodableContextTracer = context.getTracer(...);
rhinoContext.setEncodableContext(new RoProviderContext() {
@Override
public RoProviderFactory getRoProviderFactory() {
// return provider factory from JNDI
return roProviderFactory;
}
@Override
public Tracer getTracer() {
// return tracer for encode/decode methods to use
return encodableContextTracer;
}
@Override
public int getSomeContextValue() {
// return value from SBB CMP
return MySbb.this.getSomeContextValue();
}
});
....
}
// cmp field declaration
public abstract void setSomeContextValue(int value);
public abstract int getSomeContextValue();
...
}
Since other persistent entities, such as profiles and activity context interface objects, do not currently support the provision of an encodable context object, Encodable
or DatatypeCodec
types that require an encodable context cannot be used with these persistent entities. The getEncodableContext
method defined in the EncoderUtils
and DecoderUtils
interfaces will always return null for these types of persistent entities.
Codecs for Java collection types
Rhino natively supports FastSerializable
and Encodable
types, and types using a DatatypeCodec
, on array-type CMP fields. Each non-null
array element is individually serialized and deserialized as appropriate. However there are times when using a Java Collections Framework type, such as List
or Set
in a CMP field, is preferable to using an array; but retaining the serialization benefits provided by the element type is desired.
To answer this, Rhino provides a set of base classes that provides a framework for efficient serialization of Encodable
, List
, Set
, and Map
types, and additional datatype codec annotations to simplify the common use cases.
Encodable collections
Rhino provides three base abstract classes to support efficiently serialized collections:
-
com.opencloud.rhino.util.EncodableList
— implementsjava.util.List
andcom.opencloud.rhino.cmp.Encodable
-
com.opencloud.rhino.util.EncodableSet
— implementsjava.util.Set
andcom.opencloud.rhino.cmp.Encodable
-
com.opencloud.rhino.util.EncodableMap
— implementsjava.util.Map
andcom.opencloud.rhino.cmp.Encodable
.
Each of these classes wraps an implementation of the corresponding collection type, and implements the Encodable
contract to manage the serialization and deserialization of that collection. To use any of these as a CMP field type, the application developer must implement a concrete class extending from the relevant base class, taking into account the following rules and considerations.
Constructors
A concrete subclass must provide a public constructor satisfying the Encodable
contract which delegates to the equivalent protected constructor in the base class. The subclass should also provide at least one general user constructor that delegates to one of the base class public constructors, such as the no-argument constructor. A subclass of EncodableList
may also need to provide a constructor suitable for use with the implementation of the abstract newInstance
method.
Implementation of writer / reader methods
A concrete subclass must provide the implementation of the abstract writer and reader methods defined by the base class. These methods are responsible for the encoding and decoding of individual collection elements, map keys, or map values, as appropriate.
Implementation of EncodableList newInstance method
The java.util.List
interface includes a method, subList
, which returns a view of a portion of the source list. The EncodableList
requires that a subclass implements the newInstance
method it defines to facilitate the implementation of this method. The newInstance
method should return a new instance of the concrete class which wraps the list provided by the method argument. Delegating to the EncodableList
constructor defined with the same arguments as the newInstance
method is the recommended approach.
Managing null elements, keys, and values
The default implementation of EncodableList
and EncodableSet
assumes that null
elements will not occur in the collection. The default implementation of EncodableMap
assumes that null
keys will not occur in the map, but that null
values might. The consequence of this is that if a null
element occurs in a list or set, or null
key occurs in a map, then during serialization the corresponding writer method will be asked to encode the null
object. While this is not problematic, it means that the writer and reader methods need to perform additional work to handle the presence of null
objects in the stream they write to or read from.
To simplify the code required of the writer and reader methods when null
objects are expected by a given datatype, a subclass may change the default behaviour of the base class by overriding the manageNullElements
method in EncodableList
and EncodableSet
, or the manageNullKeys
and manageNullValues
methods in EncodableMap
. If these methods return true
, then the base class will check for null
objects of the corresponding type and handle them internally, only invoking the writer and reader methods for non-null
objects. If null
objects are never expected, or not supported by the underlying backing store (or the writer and reader methods will handle null
objects), then these methods may return false
, resulting in a slightly smaller serialization data stream that doesn’t include the extra information required for null
checks.
Below is an example of subclass of EncodableList
that stores a list of strings with possible null
elements:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.List;
import com.opencloud.rhino.cmp.codecs.DecoderUtils;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
import com.opencloud.rhino.cmp.codecs.SimpleDatatypeCodecs;
import com.opencloud.rhino.util.EncodableList;
public class StringList extends EncodableList<String> {
// general user constructor
// since we implicitly delegate to the default superclass constructor,
// the underlying collection will be an ArrayList
public StringList() {}
// constructor required by the Encodable contract
public StringList(DataInput in, ClassLoader cl, DecoderUtils utils) throws IOException {
super(in, cl, utils);
}
// constructor used by newInstance()
protected StringList(BackingStore backingStore, List<String> list) {
super(backingStore, list);
}
@Override
protected StringList newInstance(BackingStore backingStore, List<String> list) {
return new StringList(backingStore, list);
}
@Override
protected void writeElement(String element, DataOutput out, EncoderUtils utils) throws IOException {
CODEC.encode(element, out, utils);
}
@Override
protected String readElement(DataInput in, ClassLoader cl, DecoderUtils utils) throws IOException {
return CODEC.decode(in, cl, utils);
}
@Override
protected boolean manageNullElements() {
// null elements are possible
// tell the superclass to deal with them so we don't have to
return true;
}
// use predefined datatype codec for encode and decode
private static final SimpleDatatypeCodecs.StringCodec CODEC = new SimpleDatatypeCodecs.StringCodec();
}
Below is an example of a CMP field declared in terms of this datatype:
public abstract void setNames(StringList names);
public abstract StringList getNames();
Datatype codecs for collections
In many use cases, the implementation of an Encodable
collection type would look strikingly similar to the example shown in the previous section. Most of the implementation code is boilerplate, even more so if the writer and reader methods simply delegate to another datatype codec class. To simplify application development, Rhino provides an alternative through the use of annotations on the CMP field getter or setter method:
-
An
EncodableList
implementation can be substituted with a@ListCodecType
annotation. -
An
EncodableSet
implementation can be substituted with a@SetCodecType
annotation. -
An
EncodableMap
implementation can be substituted with a@MapCodecType
annotation.
These annotations have only two minor restrictions:
-
Element, key, and value encoding and decoding must be implemented using datatype codecs.
-
Generic type parameters aside, an annotated CMP field must be declared in terms of the base collection interface type, that is:
java.util.List
,java.util.Set
, orjava.util.Map
, or an array of any dimension of this type.-
If the CMP field type is scalar, any implementation class of the declared CMP field type may be passed as a parameter to the CMP field setter method, for example: a
java.util.ArrayList
orjava.util.HashSet
. However, no assumption can be made about the type of object returned from the CMP field getter method, other than it implements the interface declared as the CMP field type. For example, if the CMP field type isjava.util.List
, ajava.util.ArrayList
may be passed to the CMP field setter method; but it cannot be assumed that ajava.util.ArrayList
will be returned from the CMP field getter method. -
If the CMP field type is an array, any implementation class of the base component type of the declared CMP field array type may be passed as array elements. However, no assumption can be made about the type of objects returned as array elements from the CMP field getter method, other than they implement the interface declared as the array base component type.
-
Below is an example of how the StringList
CMP field example from the previous section could be simplified by using the @ListCodecType
annotation:
@ListCodecType(codec = SimpleDatatypeCodecs.StringCodec.class, manageNullElements = true)
public abstract void setNames(List<String> names);
public abstract List<String> getNames();
The annotations provide a similar level of configurability as can be achieved by manual implementation. For example, the underlying collection type and management of null
objects can be specified as annotation parameters.
Datatype codecs and null objects
If a collection datatype codec annotation indicates that null
objects should not be managed internally by Rhino, and null
objects occur in the corresponding data set, then this is one specific case where the serializing datatype codec is expected to handle and manage the null
value itself during serialization and deserialization. For simplicity reasons, it is highly recommended that Rhino be asked to manage null
objects when they are expected through appropriate annotation parameters.
Predefined datatype codecs
Rhino provides two sets of predefined datatype codecs:
-
com.opencloud.rhino.cmp.codecs.SimpleDatatypeCodecs
— provides codecs for the primitive type wrapper classes andjava.lang.String
-
com.opencloud.rhino.cmp.codecs.SleeDatatypeCodecs
— provides codecs for JAIN SLEE-defined types such asjavax.slee.SbbLocalObject
andjavax.slee.ActivityContextInterface
.
These predefined codecs can be particularly useful when using datatype codecs for collections that store simple types such as java.lang.Integer
or java.lang.String
.
Initial values
Often it is necessary, when a new persistent entity (such as an SBB entity) is created, to initialise various CMP fields to a value different than the standard Java default. For example: a CMP field storing a counter may need to be initialised to 1 instead of the default value of 0; a CMP field storing an array may need to be initialised to an empty array rather than default null
; or a CMP field storing a list may need to be initialised to an empty list. The standard way to achieve this is to add the necessary CMP setter method invocations to the entity’s create lifecycle method; this is acceptable, but means that the initial value is separated from the CMP field declaration, making the code less obvious than it could be. Also this technique doesn’t work if, for example, an arbitrary CMP extension interface is linked in to the SBB — the SBB needs to know a priori what CMP fields need initialising — which means CMP fields in CMP extension interfaces dynamically added after compilation cannot be initialised this way.
To solve this problem, Rhino introduces an annotation that can be used to specify the initial value for a CMP field.
Initial value fields
The initial value for a CMP field can be declared by annotating either the CMP field getter or setter method with the @com.opencloud.rhino.cmp.InitialValueField
annotation. The annotation references, either implicitly or explicitly by parameter, a class field that contains the initial value for the CMP field. This mechanism means that the initial value can be constructed by any legal Java means, and initial values for any arbitrarily complex CMP field type can be declared without issue.
If the annotation does not explicitly name a class field to use as the initial value, then a default name of _initial
+ the CMP field name, with the first letter capitalised, is assumed. For example, for a CMP field with the name foo
, the default initial value field name is _initialFoo
.
The initial value class field must:
-
be public, static, and final
-
be visible in the scope of the annotated CMP field accessor method
-
have a type that is assignable to the CMP field.
The @InitialValueField
annotation may be used on:
-
an SBB abstract class CMP field getter or setter method
-
an SBB or SBB part CMP extension interface CMP field getter or setter method
-
a profile CMP interface CMP field getter or setter method.
It is illegal to use this annotation on both the getter and setter methods for the same CMP field, or to declare an initial value for the same CMP field multiple times. For example, if a given CMP field is declared both in an SBB abstract class and an SBB CMP extension interface, only one CMP field declaration may be annotated with an initial value declaration. It is legal, however, for example, for two separate SBB CMP extension interfaces to both extend from a common interface that declares a CMP field with an initial value, as the initial value has only been declared once by the parent interface.
The @InitialValueField
annotation may not be used on a CMP field getter or setter method where the CMP field is one of the following types, or is an array of any dimension of one of the following types:
-
javax.slee.ActivityContextInterface
, and any subclass of this interface -
javax.slee.SbbLocalObject
, and any subclass of this interface -
javax.slee.EventContext
-
javax.slee.profile.ProfileLocalObject
, and any subclass of this interface.
A error occurs at deployment time if any of the above constraints are violated.
SLEE behaviour is undefined if an object referenced by an initial value class field is mutated at any time. A newly created persistent entity containing the CMP field could be initialised with either the original initial value or the modified initial value at the discretion of the SLEE implementation. It is generally advised that initial values be immutable; though in some cases this will not be possible.
Examples
Below is an example where the initial value field is explicitly named by the annotation:
@InitialValueField("aValue")
public abstract void setIntValue(int value);
public abstract int getIntValue();
public static final int aValue = 42;
Below is an example where the initial value field is not explicitly specified, so the default field name is assumed:
@InitialValueField
public abstract void setListValue(List<String> value);
public abstract List<String> getListValue();
public static final List<String> _initialListValue = new ArrayList<String>(5);
Pass-by-reference
The JAIN SLEE 1.1 specification, as written, prescribes that CMP fields have pass-by-value semantics. This means that when an object value is stored in a CMP field, the SLEE will make and store a copy of that value, rather than store the original value. Similarly when a stored value is retrieved from a CMP field, the SLEE will name a copy of the stored value and return the copied value. The effect of this is that the stored object is unaffected by any subsequent changes made by the application to the original or retrieved object — a value retrieved from the CMP field will always have the exact same state as it did when stored in the CMP field.
Generally speaking, this is desired behaviour, and it makes application code easier to understand. However there are times when pass-by-value semantics get in the way of efficient programming, requiring additional coding to work around the limitations of these semantics. As an example, consider an application that needs to use some unchanging session state across multiple transactions. The correct SLEE technique here is to store the state in a CMP field, then retrieve that state in each transaction as required. Unfortunately the pass-by-value semantics mean that the application incurs the overhead of a stored value copy every time it retrieves the value from the CMP field, even though the stored value is never changed. A typical workaround to avoid this overhead is to:
-
create an instance variable which caches a reference to the stored value
-
add alternative getter and setter methods which check the cache first
-
include code in the relevant lifecycle callback methods to clear any cached reference when appropriate…
…all of which is cumbersome, particularly when there are many CMP fields that require this treatment.
To simplify the application developer’s effort in situations like these, Rhino introduces the option to declare pass-by-reference semantics to CMP fields.
Reference scopes
Pass-by-reference may be declared with one of three reference scopes: TRANSACTIONAL
, WHILE_READY
, or PERMANENT
.
The TRANSACTIONAL
and WHILE_READY
reference scopes operate in similar ways. An intermediate reference cache is used to store the CMP field value during a transaction. If the CMP field is written to, the cache simply stores the reference to the stored value. If the CMP field is read, then the reference stored by the cache is returned. The actual stored value is only written back to persistent storage, and thus a copy is made, when the transaction commits; and that value is only read back from persistent storage if the CMP field is read by the application and the cache has been invalidated. The difference in the two reference scopes lies in when the cache is invalidated. When using the TRANSACTIONAL
reference scope, the reference cache is invalidated whenever a bounding transaction completes, either by commit or rollback. When using the WHILE_READY
reference scope, the reference cache is invalidated only when the owning container object (such as an SBB object) leaves the READY
state and is thus disassociated from the persistent entity, or transaction rollback occurs and the owning container object needs to resynchronise with persistent state.
The PERMANENT
reference scope completely removes any pass-by-value semantics. Any value stored in the CMP field is stored by reference only. Unlike other reference scopes, a permanent reference loses all transactional semantics. Any changes made to the referenced object will persist irrespective of whether the current transaction commits or rolls back. As such, permanent references are best suited to objects that are unlikely to change during the lifetime of an application entity, for example: parts of input network messages that must be retained across event handler transaction boundaries, but serialization of those messages is undesirable. Replication of permanent pass-by-reference CMP fields also needs to be given careful thought, as noted in the API documentation
.
Declaration
Pass-by-reference semantics are declared using the @com.opencloud.rhino.cmp.PassByReference
annotation. This annotation may be used on:
-
an SBB abstract class CMP field getter or setter method
-
an SBB or SBB Part CMP extension interface CMP field getter or setter method
-
an SBB abstract class itself
-
an SBB or SBB part CMP extension interface.
If the annotation is used on a class or interface declaration, then its meaning is applied to all CMP fields declared in the class or interface, and also any subclass if the annotation’s inherited
attribute is set to true
. An individual CMP field may override any pass-by-reference semantics inherited from a class or interface annotation by being annotated itself with a different reference scope. The reference scope DISABLED
can also be used in this case to remove any inherited pass-by-reference semantics from the CMP field.
It is illegal for a single CMP field’s getter and setter methods to be annotated with different reference scopes.
A @PassByReference
annotation with WHILE_READY
or PERMANENT
scope may not be used on a CMP field getter or setter method where the CMP field is one of the following types, or is an array of any dimension of one of the following types:
-
javax.slee.ActivityContextInterface
, and any subclass of this interface -
javax.slee.SbbLocalObject
, and any subclass of this interface -
javax.slee.EventContext
-
javax.slee.profile.ProfileLocalObject
, and any subclass of this interface.
Additional considerations
-
If pass-by-reference semantics are desired for a CMP field that holds, either directly or indirectly, a reference to a SLEE-defined object that is only valid in the transaction it was materialised (for example an
SbbLocalObject
,ProfileLocalObject
,ActivityContextInterface
, orEventContext
), then the reference scope must be limited toTRANSACTIONAL
to avoid runtime application failures. Note that if a CMP field directly storing one of these types inherits pass-by-reference semantics from a class or interface annotation, Rhino will automatically limit the scope toTRANSACTIONAL
for that CMP field. This means it is not necessary to specifically annotate a CMP field directly storing one of these types if the inherited scope is wider — the correct scope will be used automatically. -
The
@PassByReference
annotation naturally has no effect on CMP fields that store primitive types. Primitive types are always stored by value. -
The
@PassByReference
annotation also has no effect on CMP fields that store types that Rhino understands to be immutable, such asjava.lang.Integer
(and other primitive type wrappers),java.lang.String
,javax.slee.Address
, and so on… nor any class that implementscom.opencloud.util.Immutable
. Immutable types are always stored by reference.
Examples
Below is an example of how a CMP field annotation declaring pass-by-reference semantics can be applied using the default (WHILE_READY
) reference scope:
@PassByReference
public abstract void setValue(FooValue value);
public abstract FooValue getValue();
Below is an example of how a CMP field annotation declaring pass-by-reference semantics can be applied using the TRANSACTIONAL
reference scope:
@PassByReference(scope = PassByReference.Scope.TRANSACTIONAL)
public abstract void setValue(FooValue value);
public abstract FooValue getValue();
Below is an example of how an SBB abstract class annotation declaring pass-by-reference semantics can be used to apply the semantics to all CMP fields, unless otherwise indicated, using the default (WHILE_READY
) reference scope. These pass-by-reference semantics will also be inherited by any subclass of this SBB abstract class.
@PassByReference(inherited = true)
public abstract class MySbb implements Sbb {
...
// this CMP field will automatically be demoted to use the TRANSACTIONAL reference scope
public abstract void setSbbObject(SbbLocalObject sbb);
public abstract SbbLocalObject getSbbObject();
// this CMP field will revert to pass by value semantics
@PassByReference(scope = PassByReference.Scope.DISABLED)
public abstract void setValue(FooValue value);
public abstract FooValue getValue();
}
CMP field replication suppression
Normally, when an SBB entity of a replicated service is replicated between Rhino cluster nodes, the values of all SBB CMP fields form part of the replicated state. There may be some cases though where the replication of certain CMP fields is not meaningful, for example the CMP field value might refer to a resource whose identity is only meaningful on the node where that identity is obtained.
Replication of individual CMP fields can be suppressed using the @com.opencloud.rhino.cmp.CMPFieldReplication
annotation. This annotation may be used on:
-
an SBB abstract class CMP field getter or setter method
-
an SBB or SBB Part CMP extension interface CMP field getter or setter method
It is illegal for a single CMP field’s getter and setter methods to be annotated with different replication suppression modes.
If replication of a CMP field is suppressed by this annotation then the state of that CMP field is excluded from the replicated state stream if and when replication of the SBB occurs. If entities of the SBB are never replicated, then this annotation has no effect.
In the event that the CMP state of an SBB entity is retrieved from a replicated storage resource then CMP fields with their replication suppressed will revert to the initial value defined by their @InitialValueField
annotation, if present, otherwise the default initial value for the field type: null
for reference types, 0
for numeric types, etc.
Below is an example of a CMP field with replication suppressed:
@CMPFieldReplication(suppressed=true)
public abstract void setValue(FooValue value);
public abstract FooValue getValue();
CMP field tagging
CMP field tagging allows arbitrarily-named tags to be assigned to CMP fields at compile time so that they can be iterated over and processed at runtime.
It can be useful, for example, to group a set of CMP fields that have a particular function or purpose that needs to be interrogated during application execution, without the application code having to know the names of each individual CMP field, something that may not even be possible if the application is composed using dynamic bindings.
Declaration
CMP field tags are declared using the @com.opencloud.rhino.cmp.CMPFieldTag
annotation. This annotation may be used on:
-
an SBB abstract class CMP field getter or setter method
-
an SBB or SBB Part CMP extension interface CMP field getter or setter method
-
an SBB abstract class itself
-
an SBB or SBB part CMP extension interface.
If the annotation is used on a class or interface declaration, then its meaning is applied to all CMP fields declared in the class or interface.
The annotation defines:
-
a list of tags to add
-
a list of tags to remove
-
inheritance behaviour.
For annotations defined at the class or interface level, the inheritance behaviour determines whether or not tags defined by a superclass or superinterfaces will be inherited.
For annotations defined on a CMP field accessor method, if inheritance is enabled then tags defined in the following locations will be inherited:
-
Tags defined at the class or interface level in the bounding class or interface, and any tags that that class or interface inherits.
-
Tags defined by duplicate definitions of the CMP field accessor methods in superclasses.
The option of removing tags is only relevant if inheritance is enabled, as in the case inheritance is disabled then the CMP field will only be tagged with names added by the annotation.
Runtime processing
Two methods in the CMPFields
interface are provided specifically for dealing with tagged CMP fields in application code:
-
public Set<String> tags()
This method returns the names of all CMP field tags used in the CMP fields defined in the SBB, including the SBB abstract class and any CMP extension interfaces it uses. -
public void visit(String tag, CMPFieldVisitor visitor)
This method iterates over each CMP field tagged with the specified name, invoking thevisit
method on the providedvisitor
object for each one. Thevisit
method is passed aCMPField
object representing a CMP field. TheCMPField
object can be used to inspect and/or update the CMP field’s state.
CMP extension interfaces
The SLEE specification requires the SBB CMP fields be defined using abstract getter and setter methods. For example, the SBB CMP field named firstName
of type java.lang.String
must have the following method declarations in the SBB abstract class:
public abstract void setFirstName(String firstName);
public abstract String getFirstName();
Rhino allows SBB CMP fields alternatively to be defined in separate CMP extension interfaces. A CMP extension interface is simply an interface that declares methods related only to CMP fields. One use of a CMP extension interface is to allow an SBB to store additional state that may not be known when the SBB abstract class is developed. For example, the SBB build process may allow additional components to be "plugged in" to the base SBB, each of which may require its own CMP state. The plug-in components can define the CMP state they need using CMP extension interfaces; then these interfaces can be declared in the SBB deployment descriptor when the SBB is packaged.
CMP extension interfaces are also the sole mechanism by which an SBB part component can define CMP fields.
The firstName
CMP field example above could be defined in a CMP extension interface as shown below:
public interface MySbbCMPInterface {
public void setFirstName(String firstName);
public String getFirstName();
...
}
A CMP extension interface may also optionally include has
and reset
methods for each CMP field.
-
The
has
method determines if a value for the CMP field is present. Primitive types always have a value, so this method will always returntrue
for a CMP field of a primitive type. For CMP fields storing object types, this method returnstrue
if the CMP field has been assigned a non-null
value. While this method is generally for convenience only, ahas
method can offer the potential for better-performing code when only the presence of a value in the CMP field is required to be known, as the implementation does not necessarily require the stored CMP field value to be deserialized in order to test for a non-null value. -
The
reset
method resets the CMP field value to its initial value. The initial value is determined by an@InitialValueField
annotation, if present. Otherwise, the Java-defined default initial value for the field type is used; for example:0
for numeric primitives,null
for object references.
Adding the has
and reset
methods to the MySbbCMPInterface
example shown above results in the interface shown below:
public interface MySbbCMPInterface {
public void setFirstName(String firstName);
public String getFirstName();
// returns true if firstName has an assigned non-null value
// effectively equivalent to: getFirstName() != null
public boolean hasFirstName();
// effectively equivalent to: setFirstName(null)
public void resetFirstName();
...
}
Below is an example of a how the reset
method is influenced by an @InitialValueField
annotation:
public interface MySecondSbbCMPInterface {
@InitialValueField
public void setNames(String[] names);
public String[] getNames();
public static final String[] _initialNames = new String[0];
// effectively equivalent to: setNames(_initialNames)
public void resetNames();
...
}
A CMP extension interface must be public, and optionally may extend the com.opencloud.rhino.cmp.CMPFields
interface. A single SBB or SBB part may declare as many CMP extension interfaces as desired. An SBB declares its CMP extension interfaces in the oc-sbb-jar.xml
extension deployment descriptor, while an SBB part declares its CMP extension interfaces in the oc-sbb-part-jar.xml
deployment descriptor. Since a CMP extension interface defines only CMP fields, an SBB deployment descriptor does not need to specify <cmp-field>
elements for any CMP field defined only in a CMP extension interface. The CMP fields will be determined by class introspection.
All methods defined in a CMP extension interface are mandatory transactional methods. If they are invoked without a valid transaction context, a javax.slee.TransactionRequiredLocalException
will be thrown. In addition, these methods may only be invoked on an SBB or SBB part object that has been assigned to an SBB entity, or is in the process of being assigned to an SBB entity using the sbbCreate
method. If the SBB or SBB part object is not assigned to an SBB entity (with the exclusion of the sbbCreate
method), a java.lang.IllegalStateException
is thrown.
An SBB or SBB part obtains access to the CMP fields defined in CMP extension interfaces using a com.opencloud.rhino.cmp.CMPFields
object. The CMPFields
object may be typecast to any CMP extension interface declared by the SBB or SBB part, regardless of whether or not the CMP extension interface extends the CMPFields
interface, thus exposing the CMP field accessor methods defined by the interface.
The CMP fields defined by an SBB in its SBB abstract class, in any CMP extension interfaces, and in any CMP extension interfaces used by dependent SBB parts, all share the same namespace. As such, if the same CMP field is defined in multiple places, for example in the SBB abstract class and in a CMP extension interface, then it must be declared with the same type. All these CMP accessor methods will refer to the same underlying SBB CMP field.
Arbitrary CMP fields
There are sometimes occasions when an SBB needs to store arbitrary CMP state which cannot be predetermined at development time. For example, arbitrary session state could be created in response to interactions with some other network element. The typical response is to define a CMP field that stores a map and store the session state in the map as key, value pairs. This is an acceptable solution; however, serialization of the stored map value can be a performance hit if not explicitly managed; and even then a change to any mapped value requires serialization of the entire map as it is read from CMP, updated, then rewritten to CMP.
To help in these situations, Rhino allows an SBB or SBB part to optionally permit the use of arbitrary CMP fields. Arbitrary CMP fields are simply CMP fields that have not been explicitly defined in the SBB abstract class or any CMP extension interface. Support for arbitrary CMP fields not permitted by default but can be permitted using the arbitrary-cmp-fields-allowed
attribute in the oc-sbb-jar.xml
SBB extension deployment descriptor or oc-sbb-part-jar.xml
SBB part deployment descriptor. Note that if an SBB has a dependency on an SBB part that has permitted support for arbitrary CMP fields, then that permission for arbitrary CMP fields extends back to the SBB and all its dependent SBB parts. It is not possible for an SBB or SBB part to override permission for arbitrary CMP fields from some other dependent SBB part (or the SBB itself in the case of an SBB part).
Arbitrary CMP fields have only a few basic rules and restrictions:
-
Arbitrary CMP fields must have a non-null name. Any non-null name is valid, including a zero-length name.
-
An arbitrary CMP field cannot have the same name as a CMP field predefined in the SBB abstract class or any CMP extension interface.
-
An arbitrary CMP field is only deemed to exist if it has an assigned non-
null
value. An existing arbitrary CMP field ceases to exist if assigned anull
value. -
Any non-
null
value assigned to an arbitrary CMP field must be serializable using standard Java serialization.FastSerializable
types are also supported.Encodable
types and those annotated with@DatatypeCodec
, however, are not currently supported.FastSerializable
should be used instead where possible. -
Arbitrary CMP fields always exhibit pass-by-value semantics as per standard JAIN SLEE-defined CMP field behaviour. Pass-by-reference is not supported for these CMP fields.
Arbitrary CMP fields are accessed and managed using a CMPFields
object.
The CMPFields interface
The CMPFields
interface provides a means to access CMP fields indirectly by name, access metadata about CMP fields, and obtain information about and determine which CMP fields currently have a value. Arbitrary CMP fields are also managed using a CMPFields
object.
The CMPFields
interface is shown below:
package com.opencloud.rhino.cmp;
public interface CMPFields {
public <T> T get(String name)
throws NullPointerException, UnrecognisedFieldNameException, ClassCastException;
public <T> void set(String name, T value)
throws NullPointerException, UnrecognisedFieldNameException, ClassCastException;
public boolean has(String name)
throws NullPointerException, UnrecognisedFieldNameException;
public void reset(String name)
throws NullPointerException, UnrecognisedFieldNameException;
public void reset();
public Class<?> typeOf(String name)
throws NullPointerException, UnrecognisedFieldNameException;
public boolean isPredefined(String name)
throws NullPointerException;
public Set<String> predefinedNames();
public Set<String> keySet();
public Set<Map.Entry<String,Object>> entrySet();
public CMPField cmpField(String name)
throws NullPointerException, UnrecognisedFieldNameException;
public Set<String> tags();
public void visit(String tag, CMPFieldVisitor visitor)
throws NullPointerException, UnrecognisedTagNameException;
public void visit(boolean includeArbitraries, CMPFieldVisitor visitor)
throws NullPointerException;
}
The get
, set
, has
, and parameterised reset
methods allow any CMP field to be accessed by name parameter. These methods provide an alternative access mechanism for CMP fields predefined in the SBB abstract class or a CMP extension interface. These methods also allow arbitrary CMP fields to be created and managed if support for this feature is permitted by the SBB or SBB part. Predefined and arbitrary CMP fields all share the same namespace. If any of these methods are invoked with a name parameter that matches a predefined CMP field name, then the name refers to the predefined CMP field; otherwise it refers to an arbitrary CMP field. These methods throw an UnrecognisedFieldNameException
if the name refers to an arbitrary CMP field and arbitrary CMP fields are not permitted by the component.
When any of these methods are used to access a predefined CMP field, the invocation is equivalent to the corresponding CMP field method declaration. The example below illustrates this point:
public abstract class MySbb implements Sbb {
public abstract void setFirstName(String firstName);
public abstract String getFirstName();
public void someMethod() {
CMPFields cmpFields = ...
// set CMP field to "Alice"
setFirstName("Alice");
// returns "Alice"
String firstName = cmpFields.get("firstName");
// set CMP field to "Bob"
cmpFields.set("firstName", "Bob");
// returns "Bob"
firstName = getFirstName();
// as the CMP field currently contains a value,
// this will reset the CMP field to its initial value null
if (cmpFields.has("firstName") {
cmpFields.reset("firstName");
}
// returns null
firstName = getFirstName();
...
}
...
}
The unparameterised reset
method resets all predefined CMP fields to their initial value and removes all arbitrary CMP fields that exist. This method effectively returns the SBB CMP state to as it was when the SBB entity was first created.
The typeOf
method returns the Java class type of the named CMP field. For a predefined CMP field the return value is equal to the class used in its CMP field method declarations. For an arbitrary CMP field, this method returns the class of the value stored in the CMP field. The arbitrary CMP field must exist for this method to return a successful result, otherwise an UnrecognisedFieldNameException
is thrown.
The isPredefined
method returns true
if the name corresponds with a predefined CMP field, and false
otherwise. The predefinedNames
method returns a set containing the names of all predefined CMP fields.
The keySet
method returns a set containing the names of all CMP fields that currently have a value. The entrySet
method returns a map containing the names and values of all CMP fields that currently have a value. The CMP fields that are deemed to have a value include:
-
any predefined CMP field of a primitive type
-
any predefined CMP field of an object type that currently has a non-
null
value -
any arbitrary CMP field that currently exists.
The cmpField
method returns a CMPField
object that allows direct inspection and/or update to the named CMP field.
The tags
method returns the names of all CMP field tags used in the CMP fields defined in the SBB abstract class and CMP extension interfaces.
There are two visit
methods. One can be used to iterate over the set of CMP fields tagged with a specified name, invoking the visit
method on the provided visitor
object for each one. The second can be used to iterate over all CMP fields defined in the SBB abstract class and CMP extension interfaces, optionally including any arbitrarily defined CMP fields.
All methods defined in the CMPFields
interface, with the exception of isPredefined
, predefinedNames
, and tags
, are mandatory transactional methods. If they are invoked without a valid transaction context, a javax.slee.TransactionRequiredLocalException
will be thrown. In addition, these methods may only be invoked on an SBB or SBB part object that has been assigned to an SBB entity, or is in the process of being assigned to an SBB entity using the sbbCreate
method. If the SBB or SBB part object is not assigned to an SBB entity (with the exclusion of the sbbCreate
method), a java.lang.IllegalStateException
is thrown.
CMPFields object
An SBB obtains a CMPFields
object from its com.opencloud.rhino.slee.RhinoSbbContext
object (a Rhino extension of javax.slee.SbbContext
). An SBB part obtains a CMPFields
object from its com.opencloud.rhino.slee.sbbpart.SbbPartContext
object.
A CMPFields
object may be typecast to any CMP extension interface declared by the SBB or any of its dependent SBB parts, regardless of whether or not the CMP extension interface extends the com.opencloud.rhino.cmp.CMPFields
interface.
The CMPField interface
The CMPField
interface provides a means to inspect and/or update an individual CMP field.
The CMPField
interface is shown below:
package com.opencloud.rhino.cmp;
public interface CMPField {
public String getName();
public Class<?> getType();
public Set<String> getTags();
public <T> T getValue()
throws ClassCastException;
public <T> void setValue(T value)
throws NullPointerException, ClassCastException;
public boolean hasValue();
public void reset();
public boolean isPredefined();
}
The getName
and getType
methods return the name and type of the CMP field respectively. For an arbitrary CMP field, the type is determined from the CMP field’s current value.
The getTags
method returns the set of tags, if any, that have been assigned to the CMP field.
The getValue
, setValue
, hasValue
, and reset
methods perform the named function against the CMP field. For predefined CMP fields, these methods are equivalent to the get
, set
, has
, and reset
methods respectively that can be defined for a CMP field, and invoking any of these methods is equivalent to invoking the corresponding CMP field method. For example, invoking getValue()
on a CMPField
object for a field named firstName
is equivalent to invoking the getFirstName()
CMP field method.
The isPredefined
method determines whether or not the CMP field was defined in the SBB abstract class or a CMP interface, or is an arbitrary CMP field. This method returns true
if the CMPField
object represents a predefined CMP field, and false
otherwise.
The getName
, getTags
, and isPredefined
methods are non-transactional methods. This means that a valid transaction context is not required in order to invoke these methods. The getType
method is also a non-transactional method if the CMPField
object represents a predefined CMP field.
All other methods, and getType
when invoked on a CMPField
object representing an arbitrary CMP field, are mandatory transactional methods. If they are invoked without a valid transaction context, a javax.slee.TransactionRequiredLocalException
will be thrown. In addition, these methods may only be invoked on an SBB or SBB part object that has been assigned to an SBB entity, or is in the process of being assigned to an SBB entity using the sbbCreate
method. If the SBB or SBB part object is not assigned to an SBB entity (with the exclusion of the sbbCreate
method), a java.lang.IllegalStateException
is thrown.
CMPField object
An SBB can obtain a CMPField
object for any CMP field using the cmpField(String name)
method on a CMPFields
object.
A CMPField
object is also passed to the CMPFieldVisitor.visit(CMPField cmpField)
method when a visit
operation is invoked on a CMPFields
object.
An SBB obtains a CMPFields
object from its com.opencloud.rhino.slee.RhinoSbbContext
object (a Rhino extension of javax.slee.SbbContext
). An SBB part obtains a CMPFields
object from its com.opencloud.rhino.slee.sbbpart.SbbPartContext
object.
A CMPFields
object may be typecast to any CMP extension interface declared by the SBB or any of its dependent SBB parts, regardless of whether or not the CMP extension interface extends the com.opencloud.rhino.cmp.CMPFields
interface.
Application initiated persistence
Under normal conditions, transactional state of replicated activities and SBBs that gets stored into Rhino’s internal memory database automatically gets scheduled for persistence to an external key/value store if one is configured as the replication resource for the application namespace. At the early stages of a call or session though it may not make sense to replicate intermediate state changes as the state changes rapidly, for example during preconditions negotiation. Therefore, it may only be meaningful to replicate state changes once a call or session is in a so-called stable state, where the state is not expected to immediately change. Typically a session is in a semi-stable state during the alerting phase. The next stable state is answered.
An application can choose to defer the initial replication of its state at deployment time using the initial-persistence-enabled
attribute of the service-properties
element in the extension service deployment descriptor. Alternatively, an administrator can change the initial replication persistence mode of an already deployed service using the ServiceManagementMBean.setInitialPersistence
management operation.
At an appropriate time in its lifecycle, an SBB entity can enable replicated persistence of its state using RhinoSbbContext.enableEntityTreePersistence()
.
Alternatively, an SBB entity may decide that replication of its state should not occur at all. An SBB entity may permanently disable persistence of the entity tree state for its lifetime using RhinoSbbContext.disableEntityTreePersistence()
.
Persistence behaviour
Persistence of application state to a replicated storage resource such as a key/value store is decided on a transaction by transaction basis. If an SBB entity has disabled initial persistence, then all transactional state changes made during a transaction are buffered in memory by the replicated storage resource but are not scheduled for write to external storage. This means that in the event of node death prior to a write to external storage, state for those transactions will be lost and a remaining cluster node will be unable to recover that session state.
Session state includes the CMP fields of the SBB entity tree, attachment relationships to any activities, state of those activities where they are also replicated, SLEE timers, activity context name bindings, and so on.
Once the SBB entity decides that the session has reached a point where replicated persistence is appropriate, it can enable persistence using the using the enableEntityTreePersistence()
method. This will cause the current transaction to be flagged as "persistable", and updates made in that transaction, along with any previously buffered unexpired unpersisted transactions related to it will be scheduled for external persistence.
Alternatively, the SBB entity can permanently disable persistence using the disableEntityTreePersistence()
. This causes the current transaction to be flagged as "disabled for persistence", and updates made in that transaction, along with the state for any previously buffered unexpired unpersisted transactions related to it will be discarded. Future updates for that SBB entity tree and its related state will also be excluded from persistence.
There may be cases where some state for an SBB entity with initial persistence disabled has already been persisted at the point where the SBB decides that persistence should be disabled. This may happen, for example, if the maximum persist-deferred transaction age is reached in buffered state and it is force-written to external storage. In these cases, the state in external storage will remain, without further update, until the corresponding entries are removed in Rhino, for example, as SBB entities are removed, activities end, etc. |
Rules and restrictions
An SBB entity can only either enable or disable replicated persistence. Once such a decision is made, it is permanent and it cannot be reversed (unless the corresponding transaction rolls back).
Replicated persistence is managed and decided at an SBB entity tree level. This means that if a separate SBB entity tree is used to manage separate calls/sessions, then each SBB entity tree can decide at which point the state for the call/session that it itself manages should begin to be persisted or discarded.
It doesn’t matter which SBB entity in an SBB entity tree invokes the enableEntityTreePersistence()
or disableEntityTreePersistence()
method. Any SBB entity in the SBB entity tree may do so with the same effect.
The enableEntityTreePersistence()
and disableEntityTreePersistence()
methods are transactional methods. If the transaction in which one of these methods is invoked happens to roll back, then future transactions for the same SBB entity will run as if this method had never been invoked.
SLEE Facilities
Rhino provides many extensions to the standard facilities and application functions provided by the JAIN SLEE specification, as detailed below.
The AsyncLogging facility is deprecated and the asynchronous tracers provided as part of the logging framework should be used instead. |
SBB Child Relations
The JAIN SLEE specification defines that an SBB child relation requires:
-
an abstract get child relation method declared in the SBB abstract class; and
-
a
<get-child-relation-method>
deployment descriptor entry that binds the get child relation method to a particular SBB type.
The get child relation method returns a ChildRelation
object, which the SBB developer uses to create, remove, or manage the SBBs in the child relation.
A difficulty with this approach is that child relations cannot be added to an SBB without code changes to the SBB abstract class. For example, consider an SBB that delegates call processing to different child SBBs based on the protocol in use for the call (CAPv2, CAPv3, ETSI INAP CS1, and so on). Since the child SBB for each protocol needs its own get child relation method in the SBB abstract class, it’s not easy to decide at build time what protocols will be supported without modifying the SBB abstract class and recompiling. Code changes are also extremely undesirable when service bindings are used to change the child relationships of the SBB after the SBB has been installed in the SLEE.
Rhino provides an alternative mechanism to declare and use SBB child relations, which eliminates the recompilation part of the build cycle in these types of use cases. Child relations are still declared in the deployment descriptor, but the SBB accesses the child relations using a Child Relation Facility provided by the SLEE.
Extended child relation declarations
Rhino allows an SBB to declare a child relation with an extension deployment descriptor entry only; in other words, no corresponding get child relation method is needed in the SBB abstract class. As such, these child relations are termed "declarative" child relations, to differentiate them from the standard child relations defined by the SLEE specification. Declarative child relations may be added to or removed from an SBB without the need to recompile any code.
Declarative child relations are declared in the oc-sbb-jar.xml
extension deployment descriptor using the <child-relations>
element. The child-relations
element contains a child-relation
element that defines each declarative child relation. A child-relation
element contains the following sub-elements:
Sub-element | What it does |
---|---|
description |
Provides information (optional). |
child-relation-name |
Defines the name of the declarative child relation. The SBB uses this name with the Child Relation Facility to access the child relation. This name must be unique within the scope of the SBB’s declarative child relations. |
sbb-alias-ref |
References an SBB by its |
default-priority |
Specifies the default event delivery priority of the child SBB relative to its sibling SBBs. |
Child Relation Facility
The Child Relation Facility is a Rhino extension that is used by SBBs to gain access to their declarative child relations. The Child Relation Facility bypasses the need for the SBB developer to declare a get child relation method in the SBB abstract class for each child SBB that the SBB desires.
ChildRelationFacility interface
SBB objects access the Child Relation Facility through a ChildRelationFacility
object that implements the ChildRelationFacility
interface. A ChildRelationFacility
object can be obtained from the SBB’s RhinoSbbContext object (an extension of the standard SbbContext
object).
The ChildRelationFacility
interface is as follows:
package com.opencloud.rhino.facilities.childrelations;
import java.util.Collection;
import javax.slee.ChildRelation;
import javax.slee.SbbLocalObject;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.facilities.FacilityException;
import com.opencloud.rhino.slee.RhinoSbbContext;
import com.opencloud.rhino.slee.RhinoSbbLocalHome;
public interface ChildRelationFacility {
public Collection<String> getChildRelationNames()
throws FacilityException;
public ChildRelation getChildRelation(String name)
throws NullPointerException, TransactionRequiredLocalException, IllegalStateException,
UnrecognizedChildRelationException, FacilityException;
public Collection<SbbLocalObject> getChildSbbs()
throws TransactionRequiredLocalException, IllegalStateException, FacilityException;
public <T> Collection<T> getChildSbbs(Class<T> type)
throws NullPointerException, TransactionRequiredLocalException,
IllegalStateException, FacilityException;
public RhinoSbbLocalHome getChildSbbLocalHome(String name)
throws NullPointerException, UnrecognizedChildRelationException, FacilityException;
}
|
getChildRelationNames method
The getChildRelationNames
method returns the set of declarative child relation names declared by the SBB. Each name contained by this set corresponds with a name contained by a <child-relation-name>
element in the oc-sbb-jar.xml
extension deployment descriptor.
getChildRelation method
The getChildRelation
method returns a standard ChildRelation
object for the named declarative child relation. The specified name argument must be one of the names contained by the <child-relation-name>
elements in the oc-sbb-jar.xml
extension deployment descriptor; that is, it must be one of the names contained in the set of names returned by the getChildRelationNames
method.
This method performs the same function as the get child relation methods declared in the SBB abstract class for standard JAIN SLEE child relation declarations. A ChildRelation
object returned from this method can be used in exactly the same way as a ChildRelation
object returned by a get child relation method.
This method throws a NullPointerException
if the name
argument is null
. If the name
argument does not correspond with a declarative child relation, then this method throws an UnrecognizedChildRelationException
. If this method is invoked without a valid transaction context, then the method throws a TransactionRequiredLocalException
. If the method is invoked by an SBB object that is not assigned to an SBB entity, then the method throws an IllegalStateException
.
getChildSbbs methods
The getChildSbbs
methods each return a collection of child SBB local interface objects. The no-argument variant returns a collection of all child SBBs. The one-argument variant returns a collection of all child SBBs where the child SBB’s local interface is assignable to the specified Class
argument. Both these methods will consider all SBB child relations; that is, child relations declared both in the standard JAIN SLEE manner and declarative child relations.
If either of these methods are invoked without a valid transaction context then a TransactionRequiredLocalException
is thrown. If invoked by an SBB object that is not assigned to an SBB entity, then an IllegalStateException
is thrown. If the one-argument method variant is invoked with a null
argument, then the method throws a NullPointerException
.
getChildSbbLocalHome method
The getChildSbbLocalHome
method returns an object implementing the local home interface of the child SBB of the named declarative child relation. For more information on SBB local home interfaces, please see Miscellaneous SLEE Application API Enhancements.
This method throws a NullPointerException
if the name
argument is null
. If the name
argument does not correspond with a declarative child relation, then this method throws an UnrecognizedChildRelationException
.
Tracer Extensions
Rhino extends the standard javax.slee.Tracer
interface with the com.opencloud.rhino.facilities.Tracer
interface, which adds additional functionality over what the JAIN SLEE specification provides.
Tracer interface
The com.opencloud.rhino.facilities.Tracer
extended tracer interface adds two new methods to the standard tracer. Like all methods defined by the standard interface, both new methods are non-transactional; that is, they do not require an active transaction to return a successful result.
getParentTracer method
The getParentTracer
method returns a Tracer
object for the tracer’s parent tracer. The parent tracer is the tracer with the name returned by the getParentTracerName
method defined in the standard Tracer
interface.
If this method is invoked on a Tracer
object for a root tracer, then null
is returned.
getChildTracer method
The getChildTracer
method returns a Tracer
object for a tracer that is a descendant (in terms of a parent-child relationship) of the invoked tracer. The name
argument specifies the name of the child tracer.
Formally:
-
if the invoked tracer is a root tracer, then this method returns a tracer with the name specified by the
name
argument -
otherwise, this method returns a tracer with the name:
invokedTracer.getName()
+.
+name
.
The name
argument must be a valid tracer name. Since any valid name can be specified, this method can be used to create any descendant tracer — child, grandchild, and so on.
This method throws a NullPointerException
if the name
argument is null
. It throws an IllegalArgumentException
if the name
argument would result in an invalid tracer name. It throws a javax.slee.facilities.FacilityException
if the child tracer cannot be returned because of a system-level failure.
Extended Tracer Interface
See the ExtendedTracer
interface Javadoc.
Rhino extensions to the SLEE-defined Tracer interface.
Since 2.6.0 Rhino tracer extensions support use of a string interpolation format that allows deferring final string construction until it is certain that the message will be logged somewhere.
Methods taking object parameters and a string message expect the string to use "{}" to mark object insertion points. Parameters will be inserted in argument order.
tracer.info("An {} string with {} object parameters", "example", 2);
will log "An example string with 2 object parameters".
Rhino tracer extensions were written to support garbage free tracing. To support that unrolled overloads for up to 10 object parameters are provided. More than 10 object parameters to these methods should be avoided as these are incompatible with garbage free tracing.
For situations where simple string interpolation is not sufficient, we offer the full flexibility of java.util.Formatter
string formatting using the printf method.
MDC(Mapped Diagnostic Context) support was added in 2.6.0. This provides a mechanism to associate logging context with activities. associated context will be automatically included in every Trace/log message in {name=value, name=value} format. as a consequence of this, some care must be taken when deciding what to add as context to avoid overly long and unweildy log messages.
2017-11-29 12:18:42.236+1300 Fine [trace.HA_PingService.1_1.HA_PingSbb.1_1] <jr-19> {simpleRa connection=1, txID=101:211148435325409} START
Since Rhino 2.6.0 |
Usage Extensions
The JAIN SLEE specification allows SLEE components such as SBBs and resource adaptors to define a single usage parameters interface for the collection of runtime statistics. Statistics may be collected in different usage parameter sets — essentially named buckets each containing the same set of usage parameters as defined by the usage parameters interface. Creation and removal of named usage parameter sets is only supported through JMX management clients.
When building large SLEE applications or complex resource adaptors, the limitations of the SLEE-defined usage mechanism quickly becomes apparent. A single usage parameters interface lacks flexibility, and means that statistics from all parts of the system need to be lumped into a single view; and the inability of an application to be able to control its own named usage parameter sets can create a discord between any dynamic application behaviour and usage parameter set management requirements.
To alleviate these problems, Rhino provides a usage extension mechanism that allows an SBB or resource adaptor to declare multiple usage parameters interfaces, and defines a usage facility with which SBBs and resource adaptors can manage and access their own usage parameter sets. This section describes that extension mechanism.
Usage parameter types
The JAIN SLEE specification defines two types of usage parameters: counter-type and sample-type. Rhino’s extension mechanism does not add any new type of usage parameter, but does allow counter-type usage parameters to be set to a specific value rather than only incremented or decremented.
Usage parameter sets
A usage parameter set is a set that contains a usage parameter for each usage parameter name declared in the usage parameters interface of the corresponding SLEE component. Each method of the usage parameters interface declares the usage parameter name and type of a single usage parameter in this set. A SLEE component that generates usage information may access multiple usage parameters with the same lowest-level usage parameter name component, by using multiple usage parameter sets. The JAIN SLEE specification defines: a default usage parameter set, automatically available to any SLEE component that defines a usage parameters interface; and named usage parameter sets — which can also be used by the SLEE component, but can only be created and removed using the JMX management interface. Usage parameter sets in the JAIN SLEE specification occupy a flat namespace, and there is no relationship between any two usage parameter sets.
Rhino’s extension mechanism introduces a hierarchical structure and namespace for usage parameter sets. The SLEE-defined default usage parameter set is replaced with a root usage parameter set, and each usage parameter set can have zero or more child usage parameter sets. A usage parameter set name must be unique amongst its sibling usage parameter sets, but in any other case usage parameter set names may be reused.
A SLEE component creates, removes, or otherwise manages its own usage parameter sets itself using the Usage Facility and the methods defined on the Usage Parameter Interfaces.
Usage parameter set types
Each usage parameter set may or may not have a type. Usage parameter set types are declared in the deployment descriptor, each with a corresponding usage parameters interface. A usage parameter set with no type has no usage parameters, and can be used as a structural placeholder in the usage parameter set hierarchy.
The type of the root usage parameter set is also declared in the deployment descriptor. This declaration is optional. If declared, the root usage parameter set will be created with the specified type; otherwise the root usage parameter set will be created with no type.
The type of a child usage parameter set is specified at runtime when the usage parameter set is created by the SLEE component. A child usage parameter set may be created with any recognised usage parameter set type, or may be created with no type.
A SLEE component must declare at least one usage parameter set type in order to use the usage facility and manage its usage parameter sets.
Aggregation and extension
Under certain conditions, a usage parameter update to a usage parameter set may aggregate to its parent usage parameter set. Aggregation simply means that the update is also applied to the parent usage parameter set, and then its parent, and so on, so long as an aggregation relationship holds between the parent and child usage parameter sets, or the root usage parameter set is reached. Aggregation is useful, for example, to record total usage in a parent usage parameter set where individual child usage parameter sets record usage for different conditions, such as the triggering protocol of the session.
Aggregation for a given usage parameter name can only occur from a child usage parameter set to a parent usage parameter set if:
-
the parent usage parameter set and the child usage parameter set have the same usage parameter set type; or
-
the child usage parameter set type extends, either directly or indirectly, the parent usage parameter set type; and both usage parameter set types declare a usage parameter of the same type (counter-type or sample-type) with that usage parameter name.
Usage parameter set type extension is declarative rather than programmatic. A usage parameter set type declares in the deployment descriptor if it extends another usage parameter set type. The usage parameters interface of a usage parameter set type that extends another usage parameter set type is not required to extend or otherwise be related in any way to the usage parameters interface of the extended usage parameter set type. As long as the two usage parameters interfaces declare a usage parameter with the same name and type (counter-type or sample-type), then aggregation may occur between the two usage parameter set types for that usage parameter name.
Aggregation is enabled by default for all usage parameters. Aggregation can be disabled on a per usage parameter name basis using the relevant annotation on each usage parameters interface usage parameter method declaration.
Usage parameters interfaces
SLEE components declare their usage parameters using one or more usage parameters interfaces. Each usage parameters interface must be defined according to the following rules:
-
A usage parameters interface must be defined in a named package; in other words, the class must have a
package
declaration. -
A usage parameters interface must be declared as
public
. -
A usage parameters interface may optionally extend the
com.opencloud.rhino.facilities.usage.UsageParametersInterface
interface. -
Each increment, set, or sample method within the usage parameters interface must declare a lowest-level usage parameter name relevant to the SLEE component.
-
The SLEE derives the usage parameter type associated with this usage parameter name from the method name of the declared method.
-
-
Each get accessor method within the usage parameters interface provides access to the current approximate value or sample statistics for the lowest-level usage parameter name.
-
A single usage parameter name can only be associated with a single usage parameter type. The SLEE will reject a usage parameters interface that declares both a sample method and an increment or set method for the same usage parameter name.
-
It is legal to declare both increment and set methods for the same usage parameter name. These two methods simply offer alternative ways to update the same counter-type usage parameter.
-
-
A usage parameter name must be a valid Java identifier and begin with a lowercase letter, as determined by
java.lang.Character.isLowerCase
.
Counter-type usage parameter increment methods, sample-type usage parameter sample methods, and all usage parameter accessor methods, are declared in the usage parameters interface as defined in the SLEE specification.
Counter-type usage parameter set method
A usage parameter set method must be defined in the usage parameters interface to declare the presence of and to permit updates to a counter-type usage parameter. The method name of the set method is derived by adding a "set" prefix to the usage parameter name. The set method has the following method signature:
public abstract void set<usage parameter name>(long value);
|
UsageParametersInterface interface
A usage parameters interface may optionally extend the UsageParametersInterface
. By extending this interface, a usage parameters interface provides its corresponding usage parameter sets with easy access to methods reporting metadata about themselves and methods to manage their child usage parameter sets. The UsageParametersInterface
is shown below:
package com.opencloud.rhino.facilities.usage;
import java.util.Collection;
import javax.slee.SLEEException;
import javax.slee.usage.SampleStatistics;
public interface UsageParametersInterface {
public String name();
public String type();
public String key();
public <T extends UsageParametersInterface> T getOrCreateChild(String name)
throws NullPointerException, IllegalArgumentException, SLEEException;
public <T extends UsageParametersInterface> T getOrCreateChild(String name, String type)
throws NullPointerException, IllegalArgumentException,
UnrecognizedUsageParameterSetTypeException, SLEEException;
public boolean hasChild(String name)
throws NullPointerException, SLEEException;
public Collection<? extends UsageParametersInterface> children()
throws SLEEException;
public <T extends UsageParametersInterface> T parent()
throws SLEEException;
public void remove()
throws SLEEException;
}
All methods in the UsageParametersInterface
are non-transactional methods; that is, they do not require an active transaction to return a successful result, and their effects persist regardless of the outcome of any transaction active at the time of the method call.
The methods throw a SLEEException
if the requested operation cannot be performed due to a system-level failure.
name method
The name
method returns the name of the usage parameter set that the UsageParametersInterface
object is providing usage access to. This name is the name that the usage parameter set was created with. The root usage parameter set has no name; therefore this method will return null
if invoked on the root usage parameter set.
type method
The type
method returns the type name of the usage parameter set that the UsageParametersInterface
object is providing usage access to. If the usage parameter set was created with no type, this method returns null
.
key method
The key
method returns a unique identifier that identifies the usage parameter set that the UsageParametersInterface
object is providing usage access to. The key differs from the parameter set name in that the key is an absolute identifier that takes into account the usage parameter set’s place in the usage parameter set hierarchy, and is able to identify the usage parameter set without any other context; whereas the usage parameter set name is relative to the usage parameter set’s parent usage parameter set only.
getOrCreateChild methods
The getOrCreateChild
methods return the child usage parameter set with the name specified by the name
argument. If the usage parameter set already exists, then the existing usage parameter set is returned; otherwise a new usage parameter set is created. If a new usage parameter set is created, and the type
argument is specified, then the child usage parameter set is created with the specified type; otherwise it is created with the same type as the usage parameter set the method is invoked on.
These methods throw a NullPointerException
if the name
argument is null
. The methods throw an IllegalArgumentException
if the name
argument is zero-length. If the type
argument is specified and not null
, but is not recognised as a defined usage parameters interface type, the method throws an UnrecognizedUsageParameterSetTypeException
.
hasChild method
The hasChild
method returns a Boolean value indicating if a child usage parameter set with a name equal to the name
argument currently exists.
This method throws a NullPointerException
if the name
argument is null
.
children method
The children
method returns a collection containing all the child usage parameter sets of the usage parameter set that the UsageParametersInterface
object is providing usage access to.
parent method
The parent
method returns the usage parameter set that is the parent of the usage parameter set that the UsageParametersInterface
object is providing usage access to. If the UsageParametersInterface
object represents the root usage parameter set, then this method returns null
.
remove method
The remove
method removes the usage parameter set that the UsageParametersInterface
object is providing usage access to. All child usage parameter sets are also removed, recursively.
The root usage parameter set cannot be removed; however, this method may be invoked on a root usage parameter set, in which case the following behaviour is observed:
-
All child usage parameter sets are removed as usual.
-
All usage parameters in the root usage parameter set are reset to their initial value, as if the root usage parameter set had been removed and recreated in a single atomic action.
Annotations
A usage parameters interface and its usage parameter methods may all be annotated to provide additional information to Rhino’s statistics and SNMP subsystems. This is supported in both the SLEE-defined usage mechanism and Rhino’s extension mechanism. Information provided to the statistics subsystem helps clients display statistics appropriately, whereas information provided to the SNMP subsystem is used to configure the OIDs included in SNMP notifications for usage parameter set updates.
@UsageParameters annotation
A usage parameters interface may be annotated with the @UsageParameters
annotation. The @UsageParameters
annotation is shown below:
package com.opencloud.rhino.facilities.usage;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface UsageParameters {
String description() default "";
String oid() default "";
}
|
When installing a SLEE component that has default oid parameter specified, please make sure the base oid mapping is not in-use. Otherwise a duplicate oid mapping alarm will be raised. If the mapping is in-use, rhino console commands setsnmpoidmapping or removesnmpmappingconfig can be used to clear/remove it. |
@UsageCounter annotation
A counter-type usage parameter increment or set method may be annotated with the @UsageCounter
annotation. The @UsageCounter
annotation is shown below:
package com.opencloud.rhino.facilities.usage;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface UsageCounter {
String description() default "";
CounterType counterType();
boolean aggregate() default true;
String shortName() default "";
String unitLabel() default "";
int mapping() default -1;
}
|
If both an increment method and a set method are defined for a single counter-type usage parameter, and both methods are annotated with @UsageCounter
, then Rhino will arbitrarily choose one of the annotations to use for the usage parameter, and ignore the other annotation. In this case it is recommended only one of the methods be annotated.
@UsageSample annotation
A sample-type usage parameter sample method may be annotated with the @UsageSample
annotation. The @UsageSample
annotation is shown below:
package com.opencloud.rhino.facilities.usage;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface UsageSample {
String description() default "";
SampleUnits sourceUnits();
SampleUnits displayUnits();
boolean aggregate() default true;
String shortName() default "";
String unitLabel() default "";
}
|
SBB usage parameters interface deployment descriptor
If an SBB declares one or more usage parameters interfaces using the Rhino usage extension mechanism, the oc-sbb-jar.xml
Rhino extension deployment descriptor of the SBB must identify the usage parameters interfaces. The sbb-usage-parameters-interfaces
element of the SBB extension deployment descriptor identifies these interfaces. It contains the following sub-elements:
Sub-element | What it does |
---|---|
description |
This is an optional informational element. |
sbb-usage-parameters-interface |
Each usage parameters interface type defined by the SBB must be identified by a |
Each sbb-usage-parameters-interface
element has the following attributes and sub-elements:
Sub-element or attribute | What it does |
---|---|
root |
This attribute indicates if this usage parameters interface should be used as the root usage parameter set type. At most one usage parameters interface may be declared as the root usage parameter set type. |
description |
This is an optional informational element. |
sbb-usage-parameters-interface-type |
This element specifies the usage parameters interface type name. This type name identifies the usage parameters interface when creating new child usage parameter sets. Each usage parameters interface declared in the deployment descriptor must have a unique type name. |
sbb-usage-parameters-interface-name |
This element identifies the interface name of the usage parameters interface. |
The sbb-usage-parameters-interface-type
element has the following attribute:
Attribute | What it does |
---|---|
extends |
This attribute indicates if the usage parameters interface type extends another usage parameters interface type. The |
Resource adaptor usage parameters interface deployment descriptor
If a resource adaptor declares one or more usage parameters interfaces using the Rhino usage extension mechanism, the oc-resource-adaptor-jar.xml
Rhino extension deployment descriptor of the resource adaptor must identify the usage parameters interfaces. The resource-adaptor-usage-parameters-interfaces
element of the resource adaptor extension deployment descriptor identifies these interfaces. It contains the following sub-elements:
Sub-element | What it does |
---|---|
description |
This is an optional informational element. |
resource-adaptor-usage-parameters-interface |
Each usage parameters interface type defined by the resource adaptor must be identified by a |
Each resource-adaptor-usage-parameters-interface
element has the following attributes and sub-elements:
Sub-element or attribute | What it does |
---|---|
root |
This attribute indicates if this usage parameters interface should be used as the root usage parameter set type. At most one usage parameters interface may be declared as the root usage parameter set type. |
description |
This is an optional informational element. |
resource-adaptor-usage-parameters-interface-type |
This element specifies the usage parameters interface type name. This type name identifies the usage parameters interface when creating new child usage parameter sets. Each usage parameters interface declared in the deployment descriptor must have a unique type name. |
resource-adaptor-usage-parameters-interface-name |
This element identifies the interface name of the usage parameters interface. |
The resource-adaptor-usage-parameters-interface-type
element has the following attribute:
Attribute | What it does |
---|---|
extends |
This attribute indicates if the usage parameters interface type extends another usage parameters interface type. The |
Usage facility
The usage facility is used by SBBs and resource adaptors to obtain access to their root usage parameter set and to create and manage child usage parameter sets. The usage facility is defined by the com.opencloud.rhino.facilities.usage.UsageFacility
interface. An SBB obtains access to a UsageFacility
object using a JNDI name lookup. A resource adaptor obtains access to a UsageFacility
object from the ConfigProperties
object passed to it by the SLEE.
A UsageFacility
object is only made available to SLEE components that declare usage parameter interfaces using the Rhino usage extension mechanism. A SLEE component that does not declare any usage parameters interface, or declares a usage parameters interface using the SLEE-defined mechanism, will not be able to access the usage facility.
UsageFacility interface
The com.opencloud.rhino.facilities.usage.UsageFacility
interface is shown below:
package com.opencloud.rhino.facilities.usage;
import java.util.Collection;
import javax.slee.facilities.FacilityException;
public interface UsageFacility {
public static final String JNDI_NAME = "java:comp/env/slee/facilities/usage";
public static final String CONFIG_PROPERTY_NAME = "slee-vendor:com.opencloud.rhino.facilities.usage";
public <T extends UsageParametersInterface> T getRootUsageParameterSet();
public <T extends UsageParametersInterface> T getUsageParameterSet(String key)
throws NullPointerException, UnrecognizedUsageParameterSetException, FacilityException;
public <T extends UsageParametersInterface> T getOrCreateChild(UsageParametersInterface parent, String name)
throws NullPointerException, IllegalArgumentException,
UnrecognizedUsageParameterSetException, FacilityException;
public <T extends UsageParametersInterface> T getOrCreateChild(UsageParametersInterface parent, String name, String type)
throws NullPointerException, IllegalArgumentException, UnrecognizedUsageParameterSetException,
UnrecognizedUsageParameterSetTypeException, FacilityException;
public boolean hasChild(UsageParametersInterface parent, String name)
throws NullPointerException, UnrecognizedUsageParameterSetException, FacilityException;
public Collection<? extends UsageParametersInterface> getChildren(UsageParametersInterface parent)
throws NullPointerException, FacilityException;
public void removeUsageParameterSet(UsageParametersInterface paramSet)
throws NullPointerException, UnrecognizedUsageParameterSetException, FacilityException;
}
|
getRootUsageParameterSet method
The getRootUsageParameterSet
method returns the root usage parameter set for the SLEE component. If the SLEE component declares a root usage parameter set type, then the object returned from this method will be castable to the corresponding usage parameters interface for that type.
getUsageParameterSet method
The getUsageParameterSet
method returns the usage parameter set with the key specified by the key
argument. A usage parameter set’s identifying key can be obtained using the key method of the UsageParametersInterface
interface.
The usage parameter set object returned from this method will be castable to the usage parameters interface corresponding with its type, as returned by the type method of the UsageParametersInterface
interface.
This method throws a NullPointerException
if the key
argument is null
. The method throws an UnrecognizedUsageParameterSetException
if no usage parameter set currently exists with the specified key.
getOrCreateChild methods
The getOrCreateChild
methods return the child usage parameter set of the usage parameter set specified by the parent
argument with the name specified by the name
argument. If the usage parameter set already exists, then the existing usage parameter set is returned; otherwise a new usage parameter set is created. If a new usage parameter set is created, and the type
argument is specified, then the child usage parameter set is created with the specified type; otherwise it is created with the same type as the usage parameter set the method is invoked on.
These methods throw a NullPointerException
if the name
argument is null
. The methods throw an UnrecognizedUsageParameterSetException
if the parent
argument is not recognised by this usage facility object, for example if the usage parameter set was created by some other usage facility object. The methods throw an IllegalArgumentException
if the name
argument is zero-length. If the type
argument is specified and not null
, but is not recognised as a defined usage parameters interface type, the method throws an UnrecognizedUsageParameterSetTypeException
.
hasChild method
The hasChild
method returns a boolean value indicating if the usage parameter set identified by the parent
argument contains a child usage parameter set with a name equal to the name
argument.
This method throws a NullPointerException
if the name
argument is null
. This method throws an UnrecognizedUsageParameterSetException
if the parent
argument is not recognised by this usage facility object, for example if the usage parameter set was created by some other usage facility object.
getChildren method
The getChildren
method returns a collection containing all the child usage parameter sets of the usage parameter set identified by the parent
argument.
This method throws a NullPointerException
if the parent
argument is null
.
removeUsageParameterSet method
The getRootUsageParameterSet
method removes the usage parameter set identified by the paramSet
argument. All child usage parameter sets are also removed, recursively.
This method throws a NullPointerException
if the paramSet
argument is null
. This method throws an UnrecognizedUsageParameterSetException
if the paramSet
argument is not recognised by this usage facility object, for example if the usage parameter set was created by some other usage facility object.
Timer Facility Extensions
The timer facility is used by SBBs and SBB parts to manage single-shot and periodic timers. The default replication behaviour of Rhino when a timer is created depends on the replication mode of the activity that the timer is created against. Non-replicated timers are created for non-replicated activities, and replicated timers are created for activities that are replicated using the traditional savanna framework.
However, activities whose state is replicated using a key/value store only exist on one node at a time, and thus it does not make sense to replicate a timer created on such an activity across the entire cluster. Thus, Rhino’s default behaviour for timers created on a key/value store replicated activity is to not replicate those timers at all.
There are cases, however, where an application desires that such a timer survives a node failure. The firing of the timer may even be expected to trigger the retrieval of application state from the key/value store and adoption of the SBB entity tree on a surviving node after the node originally owning the SBB entity tree has failed.
In order to support replication of timers on key/value store replicated activities, Rhino extends the standard javax.slee.facilities.TimerOptions
class with the com.opencloud.rhino.facilities.RhinoTimerOptions
class. The RhinoTimerOptions
class defines two additional timer options: replication factor and convergence name session ownership record.
Replication Factor
The replication factor specifies the number of remote timer server nodes that the timer will be replicated to.
The default value of this option is 0, meaning the timer will not be replicated.
The maximum supported replication factor in the current version of Rhino is 1. A larger replication factor may be specified by an application without error, but it will be treated as if the application had specified a replication factor of 1.
Also in the current version of Rhino, the remote timer server is implemented as an internal component of a Rhino node, thus creating a replicated timer by the mechanism described here arms the timer on some other Rhino node (as well as the local node). The remote timer server node chosen for replication is chosen at random - the application has no control over which node is chosen. If there is only one node in the cluster when a timer with a non-zero replication factor is created, the local timer will still be created but a remote timer will not.
A replication factor greater than 0 can only be specified for timers set on replicated activities, but the value is ignored for activities that are not replicated using a key/value store. A replication factor less than 0 is treated as if it was 0.
If a replication factor greater than 0 is specified, then the convergence name session ownership record option must also be set in the RhinoTimerOptions
object.
Convergence Name Session Ownership Record
This option contains a reference to the convergence name session ownership record of the SBB entity tree arming the timer. This record is used to identify which Rhino node within the cluster, if any, currently owns the SBB entity tree when the replicated timer fires.
A convergence name session ownership record can be obtained from the SBB context
of the SBB, or SBB part context
of the SBB part.
This option must be specified if the replication factor option is set to a value greater that 0, otherwise it is ignored.
Profile Facility Extensions
Rhino extends the standard javax.slee.facilities.ProfileFacility
interface with the com.opencloud.rhino.facilities.profile.ProfileFacility
interface, which adds additional functionality over what the JAIN SLEE specification provides.
ProfileFacility interface
The com.opencloud.rhino.facilities.profile.ProfileFacility
interface is shown below:
package com.opencloud.rhino.facilities.profile;
import javax.slee.facilities.FacilityException;
import javax.slee.profile.UnrecognizedProfileTableNameException;
public interface ProfileFacility extends javax.slee.profile.ProfileFacility {
public boolean profileTableExists(String profileTableName)
throws NullPointerException, FacilityException;
public ProfileTableDescriptor getProfileTableDescriptor(String profileTableName)
throws NullPointerException, UnrecognizedProfileTableNameException, FacilityException;
public Class<?> getProfileLocalInterface(String profileTableName)
throws NullPointerException, UnrecognizedProfileTableNameException, FacilityException;
}
The extended interface is implemented by all ProfileFacility
objects provided by Rhino to SBBs and resource adaptors.
The extended interface adds two new methods to the profile facility. Both methods are non-transactional; that is, they do not require an active transaction to return a successful result. The methods throw the javax.slee.facilities.FacilityException
if the requested operation cannot be completed because of a system-level failure.
profileTableExists method
The profileTableExists
method returns a boolean value that reports whether or not a profile table with the name specified by the profileTableName
argument currently exists in the SLEE.
This method throws a NullPointerException
if the profileTableName
argument is null
.
getProfileTableDescriptor method
The getProfileTableDescriptor
method returns a metadata object that provides information about the profile table with the name specified by the profileTableName
argument. The ProfileTableDescriptor
interface is described below.
This method throws a NullPointerException
if the profileTableName
argument is null
. The method throws an UnrecognizedProfileTableNameException
if no profile table exists with the name specified by the profileTableName
argument.
ProfileTableDescriptor interface
The ProfileTableDescriptor
interface provides metadata information about a profile table. This information might be useful, for example, to determine if a profile table contains profiles of an expected type before querying the profile table or retrieving profiles from it.
The ProfileTableDescriptor
interface is shown below:
package com.opencloud.rhino.facilities.profile;
import javax.slee.profile.ProfileSpecificationID;
import javax.slee.profile.ProfileTable;
public interface ProfileTableDescriptor {
public ProfileSpecificationID getProfileSpecification();
public Class<? extends ProfileTable> getProfileTableInterface();
public Class<?> getProfileLocalInterface();
}
All methods in the ProfileTableDescriptor
interface are non-transactional methods.
getProfileSpecification method
The getProfileSpecification
method returns the component identifier of the profile specification of the profile table the metadata object describes.
getProfileTableInterface method
The getProfileTableInterface
method returns the Class
object of the profile table interface declared by the profile specification of the profile table the metadata object describes. If the profile specification does not declare a profile table interface, then this method returns the Class
object for the default SLEE-defined javax.slee.profile.ProfileTable
interface instead.
getProfileLocalInterface method
The getProfileLocalInterface
method returns the Class
object of the profile local interface declared by the profile specification of the profile table the metadata object describes. If the profile specification does not declare a profile local interface, then this method returns the Class
object of the profile CMP interface instead.
Null Activity Factory Extensions
Rhino does not introduce any API changes to the standard javax.slee.nullactivity.NullActivityFactory
interface, but instead offers multiple variants with different behaviours.
Each of these variants, which all implement the standard interface, can be found at different JNDI locations in an SBB’s environment.
JNDI location | Behaviour |
---|---|
|
A Null Activity Factory implementation that always creates replicated Null Activities. These activities are replicated using the namespace’s replication resource, for example savanna or key/value store replication. |
|
A Null Activity Factory implementation that always creates non-replicated Null Activities. These activities will only ever exist on the Rhino node that they were created on. |
|
At the default JAIN SLEE-defined JNDI location is a Null Activity Factory whose behaviour depends on the replication mode of the service it belongs to.
|
Activity Context Naming Facility Extensions
Rhino does not introduce any API changes to the standard javax.slee.facilities.ActivityContextNamingFacility
interface, but instead offers multiple variants with different behaviours.
Each of these variants, which all implement the standard interface, can be found at different JNDI locations in an SBB’s environment.
JNDI location | Behaviour |
---|---|
|
An Activity Context Naming Facility implementation that will bind non-replicated activities as well as those activities replicated using the namespace’s replication resource. It will only look up names in non-replicated storage and the storage associated with the namespace’s replication resource.
|
|
An Activity Context Naming Facility implementation that will only bind non-replicated activities, and will only look up bindings in non-replicated storage. Attempting to bind a replicated activity using this facility will cause a |
|
At the default JAIN SLEE-defined JNDI location is an Activity Context Naming Facility that will handle any type of activity. Name lookups will consult non-replicated storage as well as all replicated storage resources.
|
The Rhino-specific JNDI locations for the Activity Context Naming Facility are available from Rhino 2.7.0. |
Lock Facility
The lock facility allows resource adaptors to obtain transaction-based distributed locks.
In order to use the lock facility, one must obtain:
-
a reference to the
LockFacility
itself; and -
a reference to the SLEE Transaction Manager, as all locks must be obtained from within a transaction.
The following code fragment illustrates how you can obtain these references in a resource adaptor:
package ...
import javax.slee.resource.ConfigProperties;
import javax.slee.resource.ResourceAdaptor;
import javax.slee.resource.ResourceAdaptorContext;
import javax.slee.transaction.SleeTransactionManager;
import com.opencloud.rhino.facilities.lock.LockFacility;
public class FooResourceAdaptor implements ResourceAdaptor {
@Override
public void setResourceAdaptorContext(ResourceAdaptorContext context) {
// save context ref
this.context = context;
// ...
}
@Override
public void raConfigure(ConfigProperties configProps) {
// get refs to transaction manager and lock facility
txManager = context.getSleeTransactionManager();
lockFacility = (LockFacility)configProps.getProperty(LockFacility.CONFIG_PROPERTY_NAME).getValue();
// ...
}
// ...
private ResourceAdaptorContext context;
private SleeTransactionManager txManager;
private LockFacility lockFacility;
}
With these references, one can then proceed to acquire locks as necessary. For example:
private void doSomeWorkThatRequiresALock() {
// start a transaction
SleeTransaction tx = txManager.beginSleeTransaction();
try {
// acquire exclusive lock
lockFacility.acquireExclusive("SomeLock");
// do the work
// ...
// successfully completed work - commit transaction
// automatically causes the lock to be released
tx = null;
txManager.commit();
}
catch (Exception e) {
if (tx != null) {
// failed to complete work - rollback transaction
// automatically causes the lock to be released
tx.rollback();
}
}
}
Logging Context Facility
This package provides functionality for Resource Adaptors to interact with ThreadLocal
stored MDC (Mapped Diagnostic Context), also known as logging context.
What is MDC
MDC is a Map<String, String> of keys and values which will are available to the logging subsystem for various purposes. The most common and straightfoward use of MDC is printing in every log message. This provides a mechanism for providing context to log messages that would otherwise require manually passing around. A simple SIP example of useful context would be including the p-charging-vector
header. As this uniquely identifies a single call, it becomes trivial to identify all log messages related to handling an individual call.
By default Rhino logs the entire MDC on every log message.
Rhino provides transparent management of MDC in the event router threadpool. This is achieved by every Activity storing a copy of the creating thread’s MDC at activity creation. The activity stored context is restored to the EventRouter thread on beginning event processing.
Services (and service components) have a separate interface for managing MDC elements.
Resource Adaptors have access to this package to provide the tools necessary to manipulate MDC appropriately. RA internal ThreadPools do not have MDC transparently replicated across thread transitions by Rhino. This is the responsibility of the RA owning the ThreadPool.
Thread Boundaries
As MDC is stored in a ThreadLocal
Map, it will not follow over thread boundaries. For example, in the asynchronous API of any RA providing such, there will be an RA provided mechanism to hand off work to another thread (the handling thread). In order for any logging done by the handling thread to log the MDC, the handoff mechanism must support this by copying the MDC and clearing it from the handling thread when done.
What to include
As the entire MDC map is logged unconditionally with every log message human readable logs require that the MDC map be kept small. Both a small number of total entries, and the shortest practicable entires. A key should only be added to the MDC if it will provide significant value in tracking or sorting log messages,
For example, Rhino includes by default two MDC entries:
-
The current namespace (if not the default) as
ns=foo
-
The current transaction ID (if present) as
txId=101:203462114705319
The included txId is used for Rhino diagnostics, to track exactly what occurred within a given SLEE transaction, at all stages. From the service code to Rhino internals of transaction handling.
Identifiers that are short lived or not meaningful outside of the RA generally should not be added to MDC at all. And certainly not in a persistent manner except in extraordinary circumstances.
As an example of an identifier that should not be added persistently consider the SLEE TransactionID. This is not persisted within the backing Activity across multiple events, as each event takes place within a single transaction. This is a rare exception where an ephemeral key makes sense to add to MDC as processing each event involves at least one transaction, and being able to track the flow of logic within a single transaction can be extremely useful in error conditions. However, the transaction ID becomes meaningless on transaction completion, weather as a commit, or rollback. The txId key is therefore removed from MDC as soon as the transaction is completed.
Since Rhino 2.6.0 |
SAS Facility
The SAS facility provides Resource Adaptor and service developers an interface for integrating with the Metaswitch Service Assurance Server, an end-to-end tracing system. SAS provides an integrated end-to-end view of calls passing through an operator’s network. It combines traces from all network elements with reporting capability into a complete trace of the call that can be examined at multiple levels of detail to determine how the call was processed.
The principal interface for reporting data to SAS is the Trail. Trails are created by the SAS facility, either explicitly, when an RA calls startTrail()
or implicitly, when an RA or service calls getOrCreateTrail()
with an activity reference. Trails typically last the lifetime of an activity but may be shared by multiple activities, e.g. a database lookup in call setup will use the trail of the SIP dialog or transaction.
SAS trails are composed of two data message types that are reported by the network element, Events and Markers. Each event and marker in a trail is reported asynchronously to the SAS server. SAS events are functional events that affect the processing of a call, e.g. a network message or a decision made by a service and the data that was used. SAS markers are informational data about a trail, typically used for search or correlation between trails. Both events and markers can contain parameters to provide information for display, search or correlation.
Bundles and Mini-bundles
A bundle file is a YAML document mapping event names to human readable descriptions. SAS requires event decoding bundle files to display the events received. Correlation and storage do not require the bundle file, it is only used at display time.
Rhino extends the SAS bundle model to use composable mini-bundles that are assembled at runtime into a bundle to export for loading into SAS. Each network element may only use one bundle when reporting to a SAS instance so Rhino builds this bundle from all the mini-bundles found in components deployed to a namespace. Developers of resource adaptors and services that use the SAS facility must write mini-bundle files to describe the events their components report.
Mini-bundles contain a set of named event descriptions and enumerated values used to construct a SAS bundle. These are combined with system-identifying information configured in Rhino to produce the bundle SAS will use to decode messages. Each mini-bundle file starts with a version, followed by a set of events and, optionally, enums listing values that can be expanded from integer "static" parameters in event messages. A component may report events from multiple mini-bundles. Events have symbolic names to support use by multiple components. They must be packaged in a deployment jar that is installed into Rhino with the component that reports the events. This may be the same jar as the component or another that the component depends on.
After deployment the system operator exports the merged bundle and installs it into the SAS UI. At this time the component bundles are combined with the configured Resource ID and written with numeric IDs to form a bundle file SAS can load. If multiple versions of a mini-bundle are deployed, only the latest version will be used. For more information on how Rhino combines mini-bundles see SAS Bundle Generation and SAS Bundle Mappings in the Rhino Administration and Deployment Guide
It is not safe to alter the meaning or order of event or enum values between versions of a bundle. This includes removing old events, a bundle may only grow larger. |
Rhino comes with an Ant task for creating enum classes from bundle files generate-bundle-enums. Java enums are created for the SAS events and enums in the bundle file provided. The bundle files must be named for the package the enums will be created in.
Each event in a bundle must have a summary and a level. It may optionally contain details and call-flow data. Call flow descriptions must contain data, protocol and direction, other attributes are optional but should be provided where available. All text attributes of events can contain parameterised text using the Liquid templating language. For full details of the event structure contact your Metaswitch representative.
For an example of a mini-bundle file, see Service Assurance Server (SAS) Tracing in the HTTP resource adaptor guide.
Invoking Trail Accessor
On event delivery to a service, the invoking Trail is made available through the Invoking Trail Accessor. Calling getInvokingTrail()
will return the SAS trail attached to the ACI on which the event was fired. On subsequent downcalls into RAs that result in a new SLEE activity being created via SleeEndpoint.startSuspended
, the invoking trail (if one exists) will automatically be attached to the new activity. This behaviour means that a SAS trail will automatically be passed along through any number of downcalls into RAs and asynchronous events as long as the service always attaches to the ACI for the new activity.
In most cases, this behaviour is desired and saves one from having to add code that explicitly gets a trail from the invoking ACI and attaches it a new ACI. If necessary, however, one can call setInvokingTrail(Trail)
or remove()
to manually set or clear the invoking trail before calling into a RA.
Trail Association and Colocation
SAS Trails that form part of the same call can be associated by calling associate(Trail)
. If the Rhino SAS facility is configured with multiple SAS servers, different trails may not be using the same server.
Associating trails that are using the same SAS server is more efficient, as a trail association message can be sent to the one server informing it that the trails form part of the same trail group. If the trails being associated are using different servers, then a generic correlation marker gets sent to each SAS server with the UUID of the one trail. The SAS reporting then needs to perform some additional work to correlate the trails.
To ensure related trails are colocated on the same SAS server, SLEE components can call startColocatedTrail(Trail)
or startAndAssociateTrail(Trail, Scope)
. These methods will create a new SAS trail using the same server as the given trail and, in the second case, will automatically send a trail association message to the server.
Entry point to the SAS event reporting facility.
Users of this facility create a Trail
, then use that to create and report events The Trail
interface provides two ways to create then report Events and Markers:
-
create a message, add parameters, then report the message
-
convenience methods that create a Marker or Event, with various combinations of parameters, then report it, in one call.
The SAS Facility supports communication with a federation of SAS servers. As such, trails created with startTrail()
are distributed between servers by simple round-robin. When starting a new trail that will be associated with an existing one, use startColocatedTrail(Trail)
or startAndAssociateTrail(Trail, Scope)
to start a new trail on the same server as the given trail.
Since Rhino 2.6.0 |
The primary interface for creating and reporting markers and events. A trail is a sequence of related events and markers representing the processing sequence for a dialog or transaction.
Since Rhino 2.6.0 |
Provides access to the InvokingTrail. The InvokingTrail is the SAS trail attached to the activity owning the current event, at the time the event is delivered.
Since Rhino 2.6.0 |
Superinterface of EventMessage
and MarkerMessage
with functionality common to both.
A Message sent to SAS may contain parameters to be used when decoding the message on the SAS server for display or correlation.
Variable-length parameters
Thread-safety
There are some critical thread-safety issues to consider for parameters added to messages.
Parameters are not copied or marshalled into the message until report()
is called, and then only if SAS tracing is enabled.
This means two things:
-
Parameters must not be modified until the
report()
method is called. -
Large or complex objects can be passed to this method, and if tracing is disabled or the event is discarded and not reported, it will have negligible memory or CPU cost
The threadSafeParam(byte[])
method allows callers to add a parameter that will not be copied, even after report()
is called. This should be used for parameters which the caller plans to modify and has therefore defensively copied or marshalled into a new byte array.
Object parameter handling
Parameters passed to varParam(Object)
and the multi-parameter equivalents will be handled differently depending on their type:
-
null
— Encoded as zero length byte array. -
byte[]
— copied directly into the message -
java.nio.ByteBuffer
— Warning: Unsupported. Will be coerced to zero length byte array. UsethreadSafeParam(byte[])
instead. -
java.lang.String
— encoded as UTF-8 and copied into the message -
implements
EncodeableParameter
— callEncodeableParameter#encode(ByteBuffer)
copy bytes written to stream into the message -
implements
MarshalableParameter
— callMarshalableParameter#marshal()
and copy the returnedbyte[]
into the message -
any other type — call
Object#toString()
and proceed as forjava.lang.String
The varParam
method should not be used for parameters that implement EnumParameter
). It will still add the parameter but will log an error message. staticParam(EnumParameter)
should be used for enum parameters.
As noted above, this marshalling/copying happens when the report()
method is called, not when the parameters are added to the message. There are exceptions to this. These conversions happen when the parameter is added:
-
null
parameter to empty byte array -
ByteBuffer
to empty byte array -
EnumParameter
to its integer value
Since Rhino 2.6.0 |
Methods to set message fields specific to Events.
An EventMessage is a message to SAS describing a network event or processing step within a network service. EventMessages have an ID used to look up a description from a bundle deployed to the SAS server and contain parameters holding information about the state of the system that led to the event being reported.
Since Rhino 2.6.0 |
Methods to set message fields specific to Markers.
A MarkerMessage is a message sent to SAS to provide context about a trail. The marker may be used for correlating multiple trails into a trace or branch. It may also be used for searching for a trace in the SAS UI.
Some markers have special meaning to the SAS server, the START, END and FLUSH markers indicate when a trail is started, when one is ended, and when no more data is expected in the next few seconds.
Since Rhino 2.6.0 |
Ant task to generate Java enums from mini-bundle files. Contains an implicit fileset parameter that identifies the set of SAS bundle files to create Java enums for.
Task attributes are:
dir: The base directory to search for bundle files destDir: The output base directory where enum classes will be created eventsClassName: Optional. The classname for the generated events enum
See Fileset for attributes to control the selection of files parsed by the task
com/opencloud/slee/services/example/sas/sas-bundle.yaml
found in
${resources}/sas-bundles
.
The output classes will be created in ${src}
, including com.opencloud.slee.services.example.sas.SasEvent
and any enums representing SAS enums in the bundle.
<generate-bundle-enums destDir="${src}" dir="${resources}/sas-bundles" includes="com/opencloud/slee/services/example/sas/sas-bundle.yaml"/>
sas/com.opencloud.test.rhino.sas.bundle.ra.yaml
relative to
${resources}
.
The output classes will be created in ${src}
, including com.opencloud.test.rhino.sas.bundle.ra.RaSasEvent
and any enums representing SAS enums in the bundle.
<oc:generate-bundle-enums destDir="${src}"
dir="${resources}"
eventsClassname="RaSasEvent">
<include file="sas/com.opencloud.test.rhino.sas.bundle.ra.yaml"/>
</oc:generate-bundle-enums>
Session Ownership Facility
The session ownership facility allows SLEE resource adaptors to interact with the session ownership subsystem.
The principal interface to the session ownership facility is SessionOwnershipFacility
. A resource adaptor uses this facility to store, retrieve, or delete session ownership records. Each of these operations is executed asynchronously. If the resource adaptor is interested in the result of the operation (and for retrieves this is likely always the case), the resource adaptor can provide a listener object when invoking the operation. The listener object will receive a callback, typically from another thread, when the operation result becomes available.
Full API documentation for the session ownership facility is available here
.
Since Rhino 2.6.1 |
JNDI Environment
The JAIN SLEE specification defines that an SBB component has access to a JNDI API namespace where it may obtain access to various SLEE facilities and factories. Using the JNDI API for name lookups in SBB code generally works when you know exactly what you’re looking for, but an SBB that uses SBB parts may have entries in its JNDI namespace introduced by those SBB parts that may not be known at SBB compile time but are still of interest to the SBB. The JNDI API does not differentiate one binding from another; so it’s not easy for an SBB, for example, to find all resource adaptor entity bindings.
As an extension, Rhino provides all JNDI environment bindings to an SBB or SBB part in a separate map structure. The map is keyed on the fully qualified binding name, with map values containing metadata about the type of binding as well as the bound object itself.
An SBB obtains access to the JNDI bindings map from its RhinoSbbContext
object (a Rhino extension of SbbContext
):
package com.opencloud.rhino.slee;
import java.util.Map;
import javax.slee.SLEEException;
import javax.slee.SbbContext;
import com.opencloud.rhino.slee.environment.JndiBinding;
public interface RhinoSbbContext extends SbbContext {
public Map<String,JndiBinding> getJndiBindings()
throws SLEEException;
...
}
An SBB part obtains access to the JNDI bindings map from its SbbPartContext
object:
package com.opencloud.rhino.slee.sbbpart;
import java.util.Map;
import javax.slee.SLEEException;
import javax.slee.SbbContext;
import com.opencloud.rhino.slee.environment.JndiBinding;
public interface SbbPartContext extends SbbContext {
public Map<String,JndiBinding> getJndiBindings()
throws SLEEException;
...
}
JndiBinding class
The com.opencloud.rhino.slee.environment.JndiBinding
class is shown below:
package com.opencloud.rhino.slee.environment;
public abstract class JndiBinding {
public abstract BindingType getType();
public String getJndiName() { ... }
public Object getValue() { ... }
public boolean equals(Object o) { ... }
public int hashCode() { ... }
}
getType method
The getType
method returns the type of the JNDI binding. The BindingType
enumeration is shown below:
package com.opencloud.rhino.slee.environment;
public enum BindingType {
ENV_ENTRY,
ACTIVITY_CONTEXT_INTERFACE_FACTORY,
RESOURCE_ADAPTOR_ENTITY,
FACILITY,
LIMITER_ENDPOINT
}
A JNDI binding may be one of:
-
an environment entry
-
an activity context interface factory, such as the null activity context interface factory
-
a resource adaptor entity link binding
-
a SLEE facility, such as the timer facility and alarm facility
Activity factories, such as the null activity factory and service activity factory are considered to be SLEE facilities for this purpose. -
a configured Rhino limiter endpoint.
getJndiName method
The getJndiName
method returns the fully qualified name of the JNDI binding. This value is equal to the key that the JndiBinding
object is stored with in the map of bindings.
getValue method
The getValue
method returns the object bound to the JNDI name. For example, this could be an environment entry value, a SLEE facility, and so on.
Subclasses of JndiBinding
The JndiBinding
class is an abstract class. A subclass of JndiBinding
exists for each different type of binding. Each of these is described below.
EnvEntry class
The EnvEntry
class is used to describe a JNDI binding for an environment entry. The EnvEntry
class is shown below:
package com.opencloud.rhino.slee.environment;
public final class EnvEntry extends JndiBinding {
public EnvEntry(String jndiName, Object value) { ... }
public BindingType getType() { return BindingType.ENV_ENTRY; }
public String toString() { ... }
}
Facility class
The Facility
class is used to describe a JNDI binding for a SLEE facility such as the timer facility as well as activity factories such as the null activity factory. The Facility
class is shown below:
package com.opencloud.rhino.slee.environment;
public final class Facility extends JndiBinding {
public Facility(String jndiName, Object value) { ... }
public BindingType getType() { return BindingType.FACILITY; }
public String toString() { ... }
}
LimiterEndpoint class
The LimiterEndpoint
class is used to describe a JNDI binding for a configured limiter endpoint. The LimiterEndpoint
class is shown below:
package com.opencloud.rhino.slee.environment;
public final class LimiterEndpoint extends JndiBinding {
public LimiterEndpoint(String jndiName, Object value) { ... }
public BindingType getType() { return BindingType.LIMITER_ENDPOINT; }
public String toString() { ... }
}
ResourceAdaptorTypeBinding class
The ResourceAdaptorTypeBinding
class is the superclass for JNDI binding metadata classes related to resource adaptor types. The ResourceAdaptorTypeBinding
class is shown below:
package com.opencloud.rhino.slee.environment;
import javax.slee.resource.ResourceAdaptorTypeID;
public abstract class ResourceAdaptorTypeBinding extends JndiBinding {
public ResourceAdaptorTypeID getResourceAdaptorType() { ... }
public int hashCode() { ... }
}
The getResourceAdaptorType
method returns the component identifier of the resource adaptor type that the binding is associated with.
ActivityContextInterfaceFactoryBinding class
The ActivityContextInterfaceFactoryBinding
class is used to describe a JNDI binding for an activity context interface factory. The ActivityContextInterfaceFactoryBinding
class is shown below:
package com.opencloud.rhino.slee.environment;
import javax.slee.resource.ResourceAdaptorTypeID;
public final class ActivityContextInterfaceFactoryBinding extends ResourceAdaptorTypeBinding {
public ActivityContextInterfaceFactoryBinding(String jndiName, ResourceAdaptorTypeID raType, Object value) { ... }
public BindingType getType() { return BindingType.ACTIVITY_CONTEXT_INTERFACE_FACTORY; }
public String toString() { ... }
}
ResourceAdaptorEntityBinding class
The ResourceAdaptorEntityBinding
class is used to describe a JNDI binding for a resource adaptor entity. The ResourceAdaptorEntityBinding
class is shown below:
package com.opencloud.rhino.slee.environment;
import javax.slee.resource.ResourceAdaptorTypeID;
public final class ResourceAdaptorEntityBinding extends ResourceAdaptorTypeBinding {
public ResourceAdaptorEntityBinding(String jndiName, ResourceAdaptorTypeID raType, Object value) { ... }
public BindingType getType() { return BindingType.RESOURCE_ADAPTOR_ENTITY; }
public String toString() { ... }
}
Bindings
Rhino has a dynamic approach to dependency specification and binding, supported by multiple install levels for components, virtual copy of installed components, and dependency specification and application.
Dynamic component reference bindings
The JAIN SLEE specification states that every deployable component has an identity specified by a name, vendor, and version tuple. This identity must be unique within the scope of a given component type. A component may reference other components of various types by specifying their type and identity in the component deployment descriptor.
These references are static, that is: they are defined prior to installation in the SLEE; and once the component is installed the references cannot be changed. Static references in general function adequately; however they can make the application upgrade process more complicated that it need be.
For example, if a library component used by an application has a bug fixed and a new version of the library is produced, then all dependent components such as SBBs and profile specifications need to have their references updated and new versions released; and all components dependent on those, such as other SBBs and service components, also need updating and releasing; and so on through the chain of dependencies.
A more dynamic approach to dependency specification and binding would help make the upgrade process easier. Rhino provides such an approach, which is supported by the following concepts:
-
multiple install levels for components
-
virtual copy of installed components
-
dependency specification and application.
Each of these concepts is described in detail below.
Install levels
The JAIN SLEE specification defines that a deployable unit and the components it contains is either installed or not installed in the SLEE. When a deployable unit is installed, its components are verified for correctness, and if verification is successful the components are made ready for use; for example, service components are initialised ready for activation.
To facilitate the management of dynamic component bindings, Rhino provides finer-grained control over the degree to which a deployable unit and its components are installed. Installed components each have one of the following install levels:
-
INSTALLED
— The component is installed in the SLEE. The deployment descriptor has been validated for syntax and and has been parsed; however any component and class references, configuration parameters, and other relevant information contained in the deployment descriptor has not been verified for correctness. -
VERIFIED
— The component is installed and has successfully passed all verification checks. -
DEPLOYED
— The component is installed, it has passed verification, any necessary implementation code has been generated, and it has been deployed to each cluster node ready for immediate use (such as service activation).
Here are some ways you can manage install levels:
Specify the level |
The initial install level for a deployable unit and the components it contains can be specified when the deployable unit is installed. installlocaldu <file url> [-type <type>] [-installlevel <level>] [-url url] Description Install a deployable unit or other artifact. This command will attempt to forward the file content (by reading the file) to rhino if the management client is on a different host. To install something other than a deployable unit, the -type option must be specified. The -installlevel option controls to what degree the deployable artifact is installed. The -url option allows the deployment unit to be installed with an alternative URL identifier For example, using
|
||
---|---|---|---|
Verify an installed component |
An installed component can subsequently be verified using the verify <type> <url|component-id> Description Verify an installed deployable unit or component For example, using the
If the component being verified depends on other components that are installed but yet to be verified, then Rhino will automatically verify those dependent components first. (See the note about install levels below.) |
||
Verify an installed DU |
An installed deployable unit may also be "verified". Deployable unit verification is a convenience mechanism to verify all the components contained in the deployable unit with a single command. For example, using
Only the deployable unit components that have yet to be verified are affected by this command. As such, verifying a deployable unit that only contains already verified components has no further effect. |
||
Deploy a component |
A component can be deployed using the deploy <type> <url|component-id> Description Deploy an installed deployable unit or component For example, using
If the component being deployed depends on other components that are not yet deployed, then Rhino will automatically deploy those dependent components first, and so on recursively. (See the note about install levels below.) If any component being deployed has not yet been verified, then Rhino will automatically verify the component before deploying it. The deploy operation will fail if any verification errors are found in this case. |
||
Deploy an installed DU |
An installed deployable unit may also be "deployed". Deployable unit deployment is, again, a convenience mechanism to deploy all the components contained in the deployable unit with a single command. A deployable unit can be deployed in
Only the deployable unit components that have yet to be deployed are affected by this command. As such, deploying a deployable unit that only contains already deployed components has no further effect. |
||
Undeploy a deployed component |
A deployed component can subsequently be undeployed. When a component is undeployed it reverts to the A deployed component is undeployed using the undeploy <type> <component-id> Description Undeploy a deployed component For example, using
If there are any components that depend on the component being undeployed that themselves have an install level of An undeploy operation will fail if any component that would be affected by the operation does not satisfy the below requirements:
|
||
Unverify a component |
A verified or deployed component can subsequently be unverified. When a component is unverified it reverts to the A component is unverified using the unverify <type> <component-id> Description Unverify a verified component For example, using
If there are any components that depend on the component being unverified that themselves have an install level of |
About install levels
The general rule for install levels is that any given component cannot have an install level greater than any of the components that it depends on. If a component desires to transition to a higher install level then all its dependent components must successfully transition to the new install level first. If a component desires to transition to a lower install level then all components that depend on it must successfully transition to the new install level first. |
Component copy
In order to maintain compatibility with the JAIN SLEE specification, Rhino does not permit the component references of components installed using a standard SLEE deployable unit — hereinafter termed "original" components — to be dynamically modified. Rather, a virtual copy of each component to be modified must be made, and the references of the copied components modified instead. A copied component is still a SLEE component in its own right, and must have a unique identity within the scope of the component type as for any other component. The only difference between an original SLEE component and a copied component is from where the component originates in the SLEE. A copied component uses the same set of interfaces and classes, and inherits the configuration properties such as environment entries, from the component it is copied from.
Here are some commands for managing copies:
Copy |
A component can be copied using the copycomponent <type> <source-id> <target-name> <target-vendor> <target-version> [-installlevel <level>] Description Make a copy of the source component with the target identity. The -installlevel option controls to what degree the copied component is installed after it is created Example, using
The install level of the copied component can optionally be specified when the copy is made. If not specified, a default install level of |
---|---|
Show copy history |
A copied component may be subsequently copied again, leading to the treelike structure of copies branching out from the original. The getdescriptor <type> <id> Description Displays the descriptor for the specified deployable unit or component The
|
Remove copied components |
A copied component has an implicit dependency on its original. This means that a deployable unit that has copied components cannot be uninstalled from the SLEE until all its copied components have been removed. Copied components are removed using the removecopiedcomponents [<type> <url|component-id>]* Description Remove components that have been copied from another component. Either individual components or a single deployable unit identifier can be specified. In the latter case, all copied components of the DU will be removed For example, in
|
Find orphaned copied components |
After some copied components have been removed, other copied components may remain that are no longer referenced by any other component. The
|
Dynamic dependency specification
Dynamic component dependencies are specified using a binding descriptor. A binding descriptor is a JSON document that describes the changes that should be made to the deployment descriptors of one or more components. For example, a binding descriptor may change the root SBB of a service, or may add a new library reference to an SBB. A binding descriptor can only add to or change existing information contained in a deployment descriptor; it cannot remove any existing information.
A binding descriptor is a new component type in Rhino. As a component type, a binding descriptor has an identity described by the name, vendor, and version tuple — like all other SLEE component types. A binding descriptor document may be installed in Rhino as a new type of deployable entity.
Binding descriptor format
A binding descriptor is a JSON document that must conform to the schema defined by Rhino. The binding descriptor schema can be found in the doc/dtd
directory of a Rhino install. Loosely speaking, a binding descriptor document must contain the declaration of a single JSON object with:
-
an optional
description
property containing an arbitrary string description -
mandatory
name
,vendor
, andversion
properties -
an optional
service
property containing a service descriptor -
an optional
sbbs
property containing an array of zero or more SBB descriptors -
an optional
sbbParts
property containing an array of zero or more SBB part descriptors -
an optional
profileSpecs
property containing an array of zero or more profile specification descriptors -
an optional
libraries
property containing an array of zero or more library descriptors.
Each component descriptor that can be contained by a binding descriptor has a structure based on the corresponding SLEE-defined DTD for that component type. Only component properties that can be modified by bindings are defined by the schema and can be included in a binding descriptor.
Below is an example of a binding descriptor that can be used to change the root SBB of a service:
{ "description": "Change service's root SBB", "name": "Example binding descriptor", "vendor": "OpenCloud", "version": "1.0", "service": { "rootSbb": { "name": "MyOtherSBB", "vendor": "OpenCloud", "version": "1.0" } } }
Managing binding descriptors
A binding descriptor is installed into Rhino much like a SLEE deployable unit. The deployable type option must be used to indicate that the type of object being installed is a binding descriptor.
Here’s how to install and uninstall them:
Install a binding descriptor |
The example below shows how a binding descriptor can be installed using install <url> [-type <type>] [-installlevel <level>] Description Install a deployable unit jar or other artifact. To install something other than a deployable unit, the -type option must be specified. The -installlevel option controls to what degree the deployable artifact is installed
Install levels are not relevant for binding descriptors. Any install level specified when a binding descriptor is installed is ignored.
|
||
---|---|---|---|
Uninstall a binding descriptor |
An installed binding descriptor can be uninstalled in the same way as any other SLEE deployable unit. For example, using
|
Binding descriptor application
Binding descriptors are applied to components within the scope of a service. That is, a binding descriptor can be associated with a service, and its effects are propagated to the affected components used by the service. Binding descriptors can be associated with, and subsequently disassociated from, any service with an install level of INSTALLED
or VERIFIED
. A service with an install level of DEPLOYED
must be undeployed before its binding descriptor associations can be changed.
Binding descriptors can only be associated with a component copy service component. If a command is given to associate a binding descriptor with an original service component, a new copy of the service component will automatically be made by Rhino and the binding descriptor associated with the copied component instead.
A binding descriptor associated with a service that affects dependent components of the service, such as SBBs or libraries, requires that those components be copied and the effects of the binding descriptor applied to the copied components. Rhino will automatically copy components where necessary to fulfil this requirement, according to the following rules:
-
If an original component needs its bindings modified, a copy is first made and the copy modified.
-
If a copied component needs its bindings modified, and the copied component is not used in any other service, then the copied component is reused for the new modifications.
-
If a copied component needs its bindings modified, but the copied component is in use by some other service, then a new copy is made and the new copy modified.
-
The ripple effect may cause other copies to be generated. For example, if SBB A references SBB B and SBB B is copied and its bindings modified, then SBB A also needs to be modified with an update to reference the copied SBB B. To do this SBB A may also need to be copied first as described above.
The component identifiers of copied components may be specified as part of the binding operation, if specific identifiers are desired. If a component needs to be copied but a component identifier has not been specified for the copy, then Rhino will automatically generate a new unique component identifier based on the original component’s identifier.
Service binding capabilities
A binding descriptor applied to a service may cause any of the following actions:
-
change the root SBB of the service
-
modify one or more dependent SBBs by:
-
adding new library, profile specification, SBB, and/or SBB part references
-
adding new resource adaptor type bindings
-
adding new declarative SBB child relations
-
adding new CMP extension interfaces
-
adding new extension usage parameters interfaces
-
adding new environment entries, or changing the values of existing environment entries
-
-
modify one or more dependent SBB parts by:
-
adding new library, profile specification, and/or SBB part references
-
adding new resource adaptor type bindings
-
adding new CMP extension interfaces
-
adding new extension usage parameters interfaces
-
-
modify one or more dependent profile specifications by:
-
adding new library and/or profile specification references
-
adding new environment entries, or changing the values of existing environment entries
-
changing the definition or options of static queries
-
-
modify one or more dependent libraries by:
-
adding new library references.
-
Due to classloader limitations, a profile specification can only have its bindings modified if there are no profile tables, resource adaptor entities, or services with an install level of DEPLOYED , that depend on any profile specification in the same profile specification component jar present in the SLEE. Attempting to change the bindings of a profile specification that does not meet this criteria will result in the failure of the binding operation. |
Binding conflicts
It is possible that a conflict may arise with a binding descriptor that is associated with a service:
-
A binding descriptor may declare a usage parameters interface of type X, as extending a different usage parameters interface type than a previous definition of X (either in the original deployment descriptor or in another associated binding descriptor).
No conflict arises if X has previously been declared with no extends type, but an associated binding descriptor specifies an extends type for X. In this case, the SLEE assumes that X should now extend the specified type rather than extend nothing. -
A binding may declare an environment entry with name X with a different Java type than a previous definition of X.
If either of these types of conflicts occur, the binding descriptor association fails. The conflict must be resolved before the binding descriptor can be successfully associated.
Duplicate definitions such as component references do not cause a conflict. For example if the deployment descriptor and one or more binding descriptors all declare the same library reference, they are simply merged together into a single reference.
Associating a binding with a service
Here’s how to associate a binding descriptor with a service, map target component identifiers of copied component, and disassociate a binding descriptor from a service:
Associate |
A binding descriptor is associated with a service using the addservicebinding <service-id> [-binding <binding-descriptor-id>]* [-mapping <map-name>] [-dryrun] Description Add one or more bindings to a service. The -mapping option specifies a component mapping created with the createbindingcomponentmap command. The -dryrun option will display the affects the binding operation will make but will not actually commit the changes This operation may be invoked in
The example below associates a binding descriptor with a service:
|
||
---|---|---|---|
Create mappings |
The createbindingcomponentmap <map-name> Description Create a component mapping that can be used with the addservicebinding command. Mappings can be added using the addbindingcomponentmapping command. Created mappings exist only in the client, and will be lost when the client terminates removebindingcomponentmap <map-name> Description Remove an existing bindings component mapping listbindingcomponentmaps Description List the current bindings component maps addbindingcomponentmapping <map-name> <source-id> <target-name> <target-vendor> <target-version> Description Add a bindings mapping from the source component to the target identity removebindingcomponentmapping <map-name> <source-id> Description Remove a bindings component mapping dumpbindingcomponentmap <map-name> Description Dump the current mappings in the specified bindings component maps The following example creates a mapping and uses it to control the component identifier of the SBB copied by the binding operation:
|
||
Disassociate |
A binding descriptor can be disassociated from a service using the removeservicebinding <service-id> [-binding <binding-descriptor-id>]* [-dryrun] Description Remove one or more bindings from a service. The -dryrun option will display the affects the binding operation will make but will not actually commit the changes This operation may be invoked in
An example of a binding descriptor being disassociated from a service is shown below:
|
Miscellaneous SLEE Application API Enhancements
This page details the following enhancements to SLEE APIs, which Rhino provides for SLEE applications:
SbbContext interface extensions
Rhino provides an extension to the standard javax.slee.SbbContext
interface with the com.opencloud.rhino.slee.RhinoSbbContext
interface. The RhinoSbbContext
interface provides additional functionality to SBBs running in Rhino. The RhinoSbbContext
interface is as follows:
package com.opencloud.rhino.slee;
import java.util.Map;
import javax.slee.SLEEException;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.TransactionRolledbackLocalException;
import com.opencloud.rhino.cmp.CMPFields;
import com.opencloud.rhino.cmp.Encodable;
import com.opencloud.rhino.cmp.codecs.DatatypeCodec;
import com.opencloud.rhino.cmp.codecs.DecoderUtils;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
import com.opencloud.rhino.facilities.Tracer;
import com.opencloud.rhino.facilities.childrelations.ChildRelationFacility;
import com.opencloud.rhino.slee.environment.JndiBinding;
public interface RhinoSbbContext extends SbbContext {
public String getConvergenceName()
throws TransactionRolledbackLocalException, IllegalStateException, SLEEException;
public Tracer getTracer(String tracerName)
throws NullPointerException, IllegalArgumentException, SLEEException;
public RhinoActivityContextInterface[] getActivities()
throws TransactionRequiredLocalException, IllegalStateException, SLEEException;
public RhinoActivityContextInterface[] getActivities(Class<?> type)
throws NullPointerException, TransactionRequiredLocalException, IllegalStateException, SLEEException;
public ChildRelationFacility getChildRelationFacility()
throws SLEEException;
public CMPFields getCMPFields()
throws SLEEException;
public Map<String,JndiBinding> getJndiBindings()
throws SLEEException;
public <T> void setServiceContext(T context)
throws SLEEException;
public <T> T getServiceContext()
throws SLEEException;
public <T> void setEncodableContext(T context)
throws SLEEException;
public void enableEntityTreePersistence()
throws TransactionRequiredLocalException, SLEEException;
public ConvergenceNameSessionOwnershipRecord getConvergenceNameSessionOwnershipRecord()
throws TransactionRequiredLocalException, IllegalStateException, SLEEException;
public ConvergenceNameSessionOwnershipRecord getConvergenceNameSessionOwnershipRecord(long ttl)
throws TransactionRequiredLocalException, IllegalStateException, SLEEException;
}
RhinoSbbContext interface getConvergenceName method
The getConvergenceName
method returns the convergence name that the SBB entity was created with. The value returned from this method is a vendor-specific string that uniquely identifies the initial event selector conditions that led to the SBB entity’s creation.
This method only returns a non-null
value if invoked on a RhinoSbbContext
object belonging to a root SBB entity.
RhinoSbbContext interface getTracer method
The getTracer
method overrides the same method from SbbContext
to return a Rhino-specific extension of the Tracer
interface.
For more about this Tracer extension, please see Tracer extensions. |
RhinoSbbContext interface getActivities methods
The RhinoSbbContext
interface defines two getActivities
methods:
-
getActivities()
— This method overrides the same method fromSbbContext
to return a Rhino-specific extension of theActivityContextInterface
interface. ThisActivityContextInterface
extension is described in more detail below. Otherwise, this method behaves in the same way as defined by the JAIN SLEE specification. -
getActivities(Class)
— This method behaves similarly to the no-argument version; however it only returns activity context objects where the type of the underlying activity object is assignable to the class argument. For example, if this method was invoked withNullActivity.class
as an argument, then only activity context objects for the null activities the SBB entity is attached to would be returned.
RhinoSbbContext interface getChildRelationFacility method
The getChildRelationFacility
returns a Child Relation Facility object for the SBB.
For more about the Child Relation Facility, please see Child Relation Facility |
RhinoSbbContext interface getCMPFields method
The getCMPFields
method provides the SBB with access to its per-instance state.
For details on the CMPFields object returned from this method, please see The CMPFields interface. |
RhinoSbbContext interface getJndiBindings method
The getJndiBindings
method returns a map describing the JNDI bindings available to the SBB.
For more about this method, please see JNDI environment |
RhinoSbbContext interface setServiceContext and getServiceContext methods
The setServiceContext
and getServiceContext
methods allow setting and retrieving the service context object for the SBB. The service context provides an alternative storage mechanism to using static class fields for arbitrary, typically constant data that the SBB wants to share between SBB objects in the same service. The use of static class fields to store shared data becomes problematic when the encapsulating class resides in a library component jar rather than an SBB jar. In this case the static fields end up being shared between all uses of that library, rather than being scoped to a single service. The service context provides similar functionality to that of a static class field, but it is guaranteed to have visibility only within the service.
A typical use of the service context is to store data calculated by the SBB during the SBB Service Lifecycle Callbacks service lifecycle callback methods.
Any object may be stored in the service context. The service context object is stored by reference, and is never serialised. As the service context object may be accessed concurrently by different SBB objects, care must be taken that the service context object provides thread-safe access where necessary.
A service context object will persist across service deactivation and reactivation cycles unless the SBB explicitly resets the service context to null
at the appropriate time.
The visibility of a service context object is scoped to a single SBB type within a service. All SBB objects of the same SBB type share the same service context. Different SBB types within the same service may each store their own service context object without conflict.
RhinoSbbContext interface setEncodableContext method
The setEncodableContext
method sets the encodable context for the SBB.
For more about encodable contexts, please see Encodable context. |
RhinoSbbContext interface enableEntityTreePersistence method
The enableEntityTreePersistence
method enables persistence of application state to external replicated storage resources such as a key/value store. Initial replicated persistence of application state can be disabled using the service-properties
element in the extension service deployment descriptor, then enabled using this method when the SBB entity has reached a stable state.
For more about application initiated persistence, please see Application initiated persistence. |
RhinoSbbContext interface getConvergenceNameSessionOwnershipRecord methods
These methods obtain a reference to the convergence name session ownership record for the SBB entity tree.
For more about convergence name session ownership records, please see Convergence name session ownership record. |
Activity context Suspend/Resume Delivery extensions
The JAIN SLEE specification allows delivery of events to be suspended and resumed using the methods defined in the javax.slee.EventContext
interface. An obvious restriction to this is that suspending event delivery requires an EventContext
object, and the only way to obtain an EventContext
object of an unsuspended event is by event handler method argument; therefore an SBB can only suspend delivery of an event that is being delivered to it. An SBB that attaches to multiple activities may at times desire to suspend delivery of events on a selected subset of these activities while waiting for the result of some asynchronous action. The SLEE specification does not provide a trivial solution to this problem, instead requiring a complicated process of suspending events on those activities as they are received, then resuming and processing those events at an appropriate time in a later transaction.
Rhino simplifies this problem by allowing event delivery on any activity context to be suspended and resumed at any time. Rhino defines the com.opencloud.rhino.slee.RhinoActivityContextInterface
interface, an extension to javax.slee.ActivityContextInterface
, with methods providing this functionality. The RhinoActivityContextInterface
interface is shown below:
package com.opencloud.rhino.slee;
import javax.slee.ActivityContextInterface;
import javax.slee.SLEEException;
import javax.slee.TransactionRequiredLocalException;
public interface RhinoActivityContextInterface extends ActivityContextInterface {
public void suspendDelivery()
throws IllegalStateException, TransactionRequiredLocalException, SLEEException;
public void suspendDelivery(int timeout)
throws IllegalArgumentException, IllegalStateException,
TransactionRequiredLocalException, SLEEException;
public void resumeDelivery()
throws IllegalStateException, TransactionRequiredLocalException, SLEEException;
public boolean isSuspended()
throws TransactionRequiredLocalException, SLEEException;
}
All activity context objects that Rhino provides to SBBs implement RhinoActivityContextInterface
; therefore a typecast of an activity ccntext object to this interface will always succeed. Rhino will also recognise an event handler method defined with this type, instead of the standard ActivityContextInterface
, as the second method argument, removing the need to perform a typecast within the method body if the extensions are required.
An SBB or SBB Part activity context Interface may be declared as extending RhinoActivityContextInterface
rather than ActivityContextInterface
if desired.
RhinoActivityContextInterface interface suspendDelivery methods
The suspendDelivery
methods suspend further delivery of events on the invoked activity context. An activity context is only ever suspended for a specific period of time. The no-argument variant of this method suspends event delivery until some system specific default timeout is reached, while the one-argument variant suspends event delivery for a specific timeout period in milliseconds. The timeout period is measured from the time the suspendDelivery
method is invoked. Some time after the timeout period expires, delivery of events on the activity context is automatically resumed. Event delivery can also be manually resumed by an SBB before the timeout period expires, using the resumeDelivery
method.
If an SBB suspends delivery of events on an activity context for which it is currently processing an event, then event delivery of that event is suspended in the same way as if suspended using the event context associated with the event.
If an SBB suspends delivery of events on an activity context for which it is not currently processing an event, and the SLEE is already asynchronously delivering an event on that activity context to an SBB, then event delivery suspension of that activity context takes effect after the event handler method invoked on that SBB returns. If the SLEE is not currently delivering an event on that activity context, then event delivery suspension takes immediate effect.
These methods are mandatory transactional methods. The delivery of events fired on the activity context is only suspended if the enclosing transaction commits. If the transaction does not commit, then event delivery will not be suspended.
These methods throw the following exceptions:
Exception | When thrown |
---|---|
java.lang.IllegalArgumentException |
The timeout argument is zero or a negative value. |
javax.slee.IllegalStateException |
Event delivery has already been suspended on the activity context, either by a |
javax.slee.TransactionRequiredLocalException |
This method is invoked without a valid transaction context. |
javax.slee.SLEEException |
Event delivery on the activity context could not be suspended due to a system-level failure. |
RhinoActivityContextInterface interface resumeDelivery methods
The resumeDelivery
method resumes the delivery of events on the invoked activity context.
This method is a mandatory transactional method. The delivery of events occurring on the activity context is only resumed if the enclosing transaction commits. If the transaction does not commit, then event delivery will not be resumed.
This method throws the following exceptions:
Exception | When thrown |
---|---|
javax.slee.IllegalStateException |
Delivery of events on the activity context is not currently suspended; for example, if event delivery has not been suspended or has already been resumed. |
javax.slee.TransactionRequiredLocalException |
This method is invoked without a valid transaction context. |
javax.slee.SLEEException |
Event delivery on the activity context could not be resumed due to a system-level failure. |
RhinoActivityContextInterface interface isSuspended methods
The isSuspended
method determines if the delivery of events on the activity context is currently suspended. This method returns true
if the event delivery is suspended or false
otherwise.
This method is a mandatory transactional method. This method throws the following exceptions:
Exception | When thrown |
---|---|
javax.slee.TransactionRequiredLocalException |
This method is invoked without a valid transaction context. |
javax.slee.SleeException |
The event delivery status of the activity context could not be determined due to a system-level failure. |
Relationship to event context suspend/resume
The event delivery status of an event context is linked to the event delivery status of its associated activity context. If an event context is suspended, then event delivery on the activity context is also suspended, and vice versa. Similarly, if an event context is resumed, then event delivery on the activity context is also resumed. Therefore, an SBB that wants to suspend delivery of events on an activity context for which it is currently processing an event may do so in one of two ways — by invoking a suspendDelivery
method on either the event context for the received event or on the activity context that the event was delivered on.
Both these operations have the same effect. Of particular note, after either method has been invoked, both the event context and the activity context will return true
from their respective `isSuspended methods.
An SBB may also resume delivery of events on an activity context in one of two ways — by invoking the resumeDelivery
method on either the event context of the suspended event (if the event context is available) or on the activity context.
Again, both these operations will have the same effect; and after either method has been invoked, both the event context and the activity context will return false
from their respective isSuspended
methods.
SBB local home interface
The JAIN SLEE specification allows an SBB component to declare a local interface. An SBB entity can invoke a target SBB entity in a synchronous manner through the SBB local interface of the target SBB. Rhino also allows an SBB to declare a local home interface. Methods invoked on the local home interface are not specific to any SBB entity and are executed by an SBB object in the Pooled state. An SBB local home object is an instance of a SLEE-implemented class that implements an SBB local home interface.
How to get an SBB local home object
An SBB can only get an SBB local home object for its child SBBs. An SBB can get an SBB local home object for each of its child SBBs using the Child Relation Facility. The getChildSbbLocalHome
method defined by the ChildRelationFacility
interface returns an SBB local home object for the specified child SBB relation.
The RhinoSbbLocalHome interface
This interface is the base interface of all SBB local home interfaces. Currently Rhino does not allow an SBB to extend this interface; so all SBB local home interfaces, if declared, must be declared as this interface. If an SBB does not declare an SBB local home interface, then the SBB local home interface defaults to RhinoSbbLocalHome
.
The RhinoSbbLocalHome
interface is shown below:
package com.opencloud.rhino.slee;
import javax.slee.TransactionRequiredLocalException;
public interface RhinoSbbLocalHome {
public Object verifyConfiguration()
throws InvalidConfigurationException, TransactionRequiredLocalException;
public void serviceActivating(Object config)
throws TransactionRequiredLocalException;
public void serviceDeactivating()
throws TransactionRequiredLocalException;
}
If an SBB declares a local home interface in its deployment descriptor, then the local home interface methods must be implemented in the SBB abstract class using a method name constructed by capitalising the first letter of the method name as defined in this interface then prefixing sbbHome
. The method parameters and return type must be identical, and the throws clause of the implemented method must be the same as or a subset of the interface method declaration, excluding any runtime exceptions.
Although an SBB that does not declare an SBB local interface receives RhinoSbbLocalHome
as a default local home interface, such an SBB is not required to implement the local home interface methods in the SBB abstract class. The SLEE will instead provide a default no-operation implementation of these methods.
SBB service lifecycle callbacks
The RhinoSbbLocalHome
interface defines methods that allow an SBB to receive callbacks when a service it is used in is activated or deactivated. These methods are described below.
RhinoSbbLocalHome interface verifyConfiguration method
The SLEE invokes the verifyConfiguration
method on the root SBB of a service when the service is about to transition from Inactive to Active. This callback can be used by an SBB, for example, to check that its configuration in environment entries or elsewhere is valid. The SBB can throw an InvalidConfigurationException
from this method if a configuration error or other reason means that the service should not be activated at this time.
Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.
In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Active state.
If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:
public Object sbbHomeVerifyConfiguration() throws InvalidConfigurationException;
The throws
clause is optional.
-
If the method throws an
InvalidConfigurationException
when the administrator has requested activation of the service on a node that is currently operational, then the activation request fails and the service state remains unchanged. -
If the method throws an
InvalidConfigurationException
exception when a node (re)starts, where the per-node state for that node indicates that the service should be reactivated, then the reactivation attempt fails, the service transition back to the Inactive state on that node, and an alarm is raised to indicate that the service could not be activated.
The return result from this method is passed as an argument to the serviceActivating
method if the service successfully activates. This object can be used, for example, to pass some configuration information calculated during this method to the serviceActivating
method if that method would otherwise need to recalculate the same information again.
This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context then a TransactionRequiredLocalException
is thrown.
RhinoSbbLocalHome interface serviceActivating method
The SLEE invokes the serviceActivating
method on the root SBB of a service when the service is about to transition from Inactive to Active. This method is invoked after the verifyConfiguration
method has returned successfully and the SLEE has determined that the service activation can proceed to completion. The SBB can use this callback, for example, to initialise common state shared between SBB objects (such as the service context of the same SBB).
Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.
In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Active state. The service transitions to the Active state on the corresponding node after this method returns.
If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:
public void sbbHomeServiceActivating(Object config);
When this method is invoked by the SLEE, the object passed as an argument to this method is the return result from the previous corresponding verifyConfiguration
method invocation.
This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context then a TransactionRequiredLocalException
is thrown.
RhinoSbbLocalHome interface serviceDeactivating method
The SLEE invokes the serviceDeactivating
method on the root SBB of a service when no SBB entity trees remain in the service and it is about to transition from the Stopping state to the Inactive state. An SBB can use this callback, for example, to clean up any shared state that is no longer required after the service has deactivated.
Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.
In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Inactive state. The service transitions to the Inactive state on the corresponding node after this method returns.
If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:
public void sbbHomeServiceDeactivating();
This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context, then a TransactionRequiredLocalException
is thrown.
Lifecycle callback method invocation cascade
The SBB service lifecycle callback methods are initially invoked by the SLEE on the root SBB of a service. If the root SBB has child SBB relations where the cascade-service-lifecycle-callbacks
option in the SBB extension deployment descriptor is set to True
, then the SLEE will also automatically invoke the same lifecycle callback method on each of those child SBBs. This process repeats or "cascades" to each child SBB in the service where the cascade-service-lifecycle-callbacks
option in the parent SBB requests it. The SLEE however will invoke this method at most once for each SBB type present in the service, regardless of how many times a given SBB appears as a child SBB in the service.
Lifecycle callback method invocation cascade is enabled by default on all child relations. Cascade may be disabled for a given child SBB relation by setting the corresponding cascade-service-lifecycle-callbacks
option in the SBB extension deployment descriptor to False
. As an alternative to automatic cascade, an SBB can manually invoke the lifecycle callback methods directly on any of its child SBBs by obtaining the child SBB’s local home object from the Child Relation Facility. Lifecycle callback methods invoked by an SBB, rather than the SLEE, do not cascade to other child SBBs, even if those child SBB relations are flagged for cascade.
Lifecycle callback method invocations proceed to cascade through eligible child SBB relations irrespective of whether or not the root SBB or any given child SBB declares a local home interface. An SBB that does not declare a local home interface is simply unaware that the callback method invocation occurred.
Per-Node Service Activity and Lifecycle Events
Since Rhino 2.6.1 |
Service Node Activities can be used by SBBs to monitor the managed lifecycle of the service they are a part of on their individual cluster node. Service Node Activities perform the same function as SLEE 1.1 Service Activities, but for each node in a Rhino cluster instead of only once for the whole cluster. This is useful when a service needs to initialise local state on each node in a cluster or perform shutdown tasks without storing replicated SBB state.
When a service is started on a node, a Service Node Activity
is created on that node and a com.opencloud.rhino.slee.servicenodeactivity.ServiceNodeStartedEvent
is fired on the activity to the service. When the service is stopped on a node, the service node activity on that node is ended and a javax.slee.ActivityEndEvent
is fired on the activity. A service starts on a node when either of the following occurs on that node:
-
the SLEE is in the Running state and the service is activated via the
ServiceManagementMBean
; or -
the persistent state of the service says that the service should be active, and a previously stopped SLEE transitions to the Running state.
The event type name of Service Node Started events is com.opencloud.rhino.slee.servicenodeactivity.ServiceNodeStartedEvent
, the vendor is com.opencloud
, and the version is 1.0
. The event, when fired by the SLEE, will only be delivered to SBBs in the service that is starting, and not any other service.
Service Node Activities are never replicated. Each node has their own Service Node Activity that does not share state with any other node.
The Service Node Activity allows the SBBs making up the service to identify the service. The Service Node Activity for an SBB entity can be obtained by looking it up using the ServiceNodeActivityFactory
class, an instance of which can be obtained by JNDI lookup using the name java:comp/env/rhino/servicenodeactivity/factory
. An SBB can create an Activity Context Interface for the Service Node Activity using the ServiceNodeActivityContextInterfaceFactory
class. An instance of this can be looked up using the JNDI name java:comp/env/rhino/servicenodeactivity/activitycontextinterfacefactory
.
Unchecked throwable propagation
The JAIN SLEE specification states that if a method invocation on an SBB or a profile returns by throwing an unchecked exception, then (amongst other things) the transaction is marked for rollback and a javax.slee.TransactionRolledBackLocalException
is propagated back to the caller. There may be times, however, in certain applications where this behaviour is undesirable, and the caller would rather catch and handle the exception itself rather than have the transaction forcibly rolled back.
To address this need, Rhino provides the @PropagateUncheckedThrowables
annotation. This annotation can be used on any SBB local interface method or profile local interface method to indicate that any unchecked throwable (RuntimeException
s or Error
s) produced by the method must be propagated back to the caller as-is. The transaction is not marked for rollback, and the invoked SBB or profile object remains in the same state; in other words, it is not discarded by a transition to the Does Not Exist state.
An SBB local interface or profile local interface class declaration may also be annotated with @PropagateUncheckedThrowables
. When used in this way, the annotation indicates that all methods defined in the interface, and all inherited methods, shall exhibit the behaviour defined by the annotation, as if all these methods were annotated individually.
The propagation of unchecked throwables only applies to exceptions produced by the invoked method itself. The annotation has no effect against container-generated exceptions that cause rollback, such as trying to invoke a local interface method on an SBB that no longer exists. The annotation is also ignored for methods originally defined in the base local interfaces: javax.slee.SbbLocalObject
and javax.slee.profile.ProfileLocalObject
.
Convergence name session ownership record
Since Rhino 2.6.1 |
A convergence name session ownership record is a session ownership record related to an SBB entity tree. These records are managed by Rhino using the session ownership store on behalf of each SBB entity tree. This means that applications do not need to create, update, or store the record directly themselves, they simply ask for the record and modify attributes on it in a CMP-style fashion as desired. Modifications to the underlying record will be stored automatically by Rhino during transaction commit, and the record will be automatically deleted when the SBB entity tree is removed.
The convergence name session ownership record for the current SBB entity tree can be obtained from the SBB context
of an SBB, or SBB part context
of an SBB part.
An application interacts with a convergence name session ownership record using the ConvergenceNameSessionOwnershipRecord
interface. The ConvergenceNameSessionOwnershipRecord
interface is shown below:
package com.opencloud.rhino.facilities.sessionownership;
import java.util.Map;
import com.opencloud.rhino.slee.ConvergenceName;
public interface ConvergenceNameSessionOwnershipRecord {
public String getPrimaryKey();
public ConvergenceName getConvergenceName();
public Map<String,String> getAttributes();
public String getAttribute(String name);
public void setAttributes(Map<String,String> attributes, boolean exclusive)
throws NullPointerException;
public void setAttribute(String name, String value)
throws NullPointerException;
public boolean removeAttribute(String name);
public long getTimeToLive();
public void setTimeToLive(int ttl)
throws IllegalArgumentException;
}
Note that this interface does not extend and is not related to the SessionOwnershipRecord
interface used by the session ownership resource adaptor type. This is intentional to avoid a convergence name session ownership record — a Rhino-managed record — to be manipulated in the same way as a regular non-managed session ownership record. A convergence name session ownership record is intended to be manipulated by applications in a CMP-like manner, the various getter and setter methods on the interface serving to reinforce this mentality.
ConvergenceNameSessionOwnershipRecord interface getPrimaryKey method
The getPrimaryKey
method returns the primary key of the session ownership record.
The main reason for exposing this key is to allow other protocol-specific session ownership records to link to this record as a master record of session ownership.
It is strongly discouraged that this key be used with the session ownership resource adaptor type to retrieve, update, or other manipulate the underlying session ownership record. Any manual update of the underlying record by an application will be overwritten by Rhino when it synchronises the managed record state with the underlying record state.
ConvergenceNameSessionOwnershipRecord interface getConvergenceName method
The getConvergenceName
method returns a ConvergenceName
object that describes the convergence name of the SBB entity tree associated with the record.
ConvergenceNameSessionOwnershipRecord interface get/set/removeAttribute methods
These methods get, set, or remove a single application-defined attribute in the record.
Only application-defined attributes may be manipulated by an application. While a convergence name session ownership record may define other attributes for internal use, these are not visible to application components.
ConvergenceNameSessionOwnershipRecord interface getAttributes method
The getAttributes
method returns a map of attribute name to attribute value for all the application-defined attributes stored in the record.
ConvergenceNameSessionOwnershipRecord interface setAttributes method
The setAttributes
method allows multiple application-defined attributes to be set in the record at once using a map of attribute names to attribute values.
If the exclusive
method argument is true
, then any existing application-defined attributes in the record are removed before the attributes specified in the map are set. In other words, after this method returns, the only application-defined attributes contained in the record will be those contained in the map passed as an argument to the method.
ConvergenceNameSessionOwnershipRecord interface get/setTimeToLive methods
All session ownership records have a time-to-live (TTL) period after which they expire and are deleted. The initial TTL of a convergence name session ownership record is set when the record is first obtained, but the TTL may be changed at any time by either:
-
reobtaining the record from the
SBB context
orSBB part context
with the new TTL; or -
invoking the
setTimeToLive
method on an already obtainedConvergenceNameSessionOwnershipRecord
object with the new TTL.
The current record TTL, measured in milliseconds, can be obtained using the getTimeToLive
method.
Convergence name session ownership record continuity
Rhino will use the TTL of a convergence name session ownership record to determine if and when a record needs refreshing to ensure its continued survival for the lifetime of the SBB entity. However, such checks are only made after an SBB entity tree processes an event. This means that an application should set the TTL of this record to a value greater than the maximum expected time between received events.
If a convergence name session ownership record expires, then all application-defined attributes will be lost.
Coordinated session adoption
Many SLEE applications use multiple types of activities within a single service instance (or SBB entity tree). For example there may be activities for call signaling, charging, and so on. The resource adaptors that manage each of these activities may maintain their own session ownership records in addition to the convergence name session ownership record managed by Rhino.
In a session failover scenario, there must be a coordinated approach to how a new node adopts a session such that race conditions do not occur. For example, if a signaling event is received on an activity by one surviving node at the same time a charging event for the same session is received on another related activity on another node, there must be a mechanism for resolution such that the session only gets adopted by one of the nodes, otherwise application state becomes fragmented and inconsistent.
The purpose of the convergence name session ownership record is to allow the protocol sessions (and their internal state variables), as well as the application state (SBB entity CMP variables) to move between SLEE nodes together, to preserve sticky-sessions. During failover, adoption of a session is controlled by a race for ownership, and the convergence name session ownership record is the single point of contact for that race.
The mechanism as a whole is intended to work as follows:
-
Each resource adaptor that supports failover creates and manages a session ownership record for each activity or set of related activities. A protocol-specific mapping is used to obtain a session ownership record primary key from an activity handle or identifier.
-
These resource adaptors also provide an API that allows an application to indicate the protocol-specific session ownership record should be linked with its convergence name session ownership record. For example, the resource adaptor could provide a method that takes an activity object and a convergence name session ownership record primary key as arguments.
-
The resource adaptor then links its protocol-specific session ownership record with the convergence name session ownership record. It does this by setting an attribute on the protocol-specific record with the value of the convergence name record primary key passed in by the application. It is recommended that resource adaptors use the
SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME
attribute for this purpose but this is not mandatory. -
When the resource adaptor receives an event on an activity for which it has no local state, it retrieves the corresponding protocol-specific session ownership record and extracts the primary key of the convergence name session ownership record from that. The resource adaptor then uses the
SessionOwnershipFacility.tryAdoptRecord
method, passing in the convergence name record primary key, to try to claim ownership of the convergence name session ownership record.-
If the adoption attempt results in the application session being owned by the current node, then the resource adaptor restores any activity session state that it needs to and continues to process the event locally.
-
If not, the adoption result may indicate that the event should be forwarded to another cluster node, or that the adoption attempt was inconclusive and may need to retried in a short period of time. A resource adaptor should limit the maximum number of times it will retry an adoption attempt before giving up and aborting the session.
-
Session adoption pseudo-code
The pseudo-code shown below illustrates how a resource adaptor is expected to use the session ownership facility to adopt a session. Although the session ownership facility only provides asynchronous operations for record retrieval, this pseudo-code is written assuming synchronous operations for the purposes of brevity and clarity.
SessionOwnershipFacility sessionOwnershipFacility = ...;
// this method is called when a network event has been parsed
// but no other "receive logic" has executed yet
void onRequest(Request request, SessionID sessionID) {
if (haveLocalState(sessionID)) {
// no need to care about session ownership because the session state is already here
processRequestHere(request, sessionID);
return;
}
// note: the real retrieveRecord() operation here is asynchronous
SessionOwnershipRecord protoSpecificRecord = sessionOwnershipFacility.retrieveRecord(sessionID.toTrackingKey());
if (protoSpecificRecord == null || protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME) {
log.debug("do not have a protocol specific record, or don't have an associated-record attribute");
sendTemporaryErrorResponse(request, sessionID);
return;
}
boolean success = tryAdopt(request, sessionID, protoSpecificRecord);
if (!success) {
log.debug("adoption loop gave up");
sendTemporaryErrorResponse(request, sessionID);
return;
}
log.debug("successfully handled request");
}
// this method attempts to adopt the session
// it returns true if successful, false if we gave up
boolean tryAdopt(Request request, SessionID sessionID, SessionOwnershipRecord protoSpecificRecord) {
String convergenceNamePKey = protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME);
boolean finishedAdopt = false;
int adoptCount = 0;
// other nodes may have received a request for a related record too, so we need to race
SessionOwnershipAdoptionResult adoptRes;
while (!finishedAdopt && adoptCount <= 3) {
adoptRes = sessionOwnershipFacility.tryAdoptRecord(convergenceNamePKey);
log.debug("tryAdoptRecord(%s) result is %s", convergenceNamePKey, adoptRes);
switch (adoptRes.getType()) {
case ALREADY_OWNED_BY_THIS_NODE:
log.debug("I am already owner for session %s", sessionID);
finishedAdopt = true;
processRequestHere(request, sessionID);
return true;
case ALREADY_OWNED_BY_OTHER_NODE:
log.debug("Another node %s is already owner for session %s", adoptRes.getNodeID(), sessionID);
finishedAdopt = true;
break;
case RACE_WON_BY_THIS_NODE:
// we won the race - process it here
// to process it here we have to
// 1) adjust the protocol specific record to say this node is the owner
// so that future reads of that record note the correct owner for the record
// 2) process the request here
log.debug("I won the race for session %s", adoptRes);
finishedAdopt = true;
String previousOwner = findLocalOwnerURI(protoSpecificRecord.getOwnerUris());
String newOwner = computeOwnerURI(request);
SessionOwnershipRecord newProtoSpecificRecord = protoSpecificRecord
.toBuilder()
.removeOwnerURI(previousOwner)
.addOwnerURI(newOwner)
.build();
// we don't need to wait for the result of this operation
// if it fails there's nothing we can do and if it succeeds we don't need the latency
sessionOwnershipFacility.storeRecord(newProtoSpecificRecord, myListener);
processRequestHere(request, sessionID);
return true;
case RACE_WON_BY_OTHER_NODE:
// another node won the race, back off and start the read again
break;
case RECORD_VIEW_ID_NEWER:
// current node is behind a more recent cluster view change
// back off and try the read again
break;
case RECORD_NOT_APPROPRIATE:
// the convergence name pkey did not identify an appropriate convergence name record for the race
break;
case RECORD_DOES_NOT_EXIST:
// the convergence name record was identifed but does not exist in the session ownership store
break;
case RECORD_NOT_SAME_CLUSTER:
// the related record is not in the same cluster
break;
case SYSTEM_ISSUE:
// an internal error occurred, back off and retry
break;
}
if (!finishedAdopt) {
Thread.currentThread().sleep(INTER_CAS_SLEEP);
}
adoptCount++;
}
if (!finishedAdopt) {
// we've retried several times and it didn't work, give up
return false;
}
// now we know the adoption result
// if it was won by this node, or already owned by this node, we'd have processed and returned
// now we have to wait for the protocol specific record to catch up
// first use the record we've already got and see if in sync
if (protocolRecordAndAdoptionResultInSync(protoSpecificRecord, adoptRes)) {
// they are in sync, proxy and return
moveRequestSideways(request, protoSpecificRecord);
return true;
}
// the first protocol specific record was not in sync, so loop on that waiting for it to be updated
boolean inSyncProtocolRecord = false;
int readCount = 0;
while (!inSyncProtocolRecord && readCount <= 3) {
// note: the real retrieveRecord() operation here is asynchronous
SessionOwnershipRecord protoSpecificRecord = sessionOwnershipFacility.retrieveRecord(sessionID.toTrackingKey());
if (protoSpecificRecord == null || protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME) {
log.debug("do not have a protocol specific record, or don't have an associated-record attribute");
return false;
}
if (protocolRecordAndAdoptionResultInSync(protoSpecificRecord, adoptRes)) {
moveRequestSideways(request, protoSpecificRecord);
return true;
}
else {
Thread.currentThread().sleep(INTER_PROTO_READ_TIMEOUT);
}
readCount++;
}
// give up
return false;
}
void moveRequestSideways(Request request, ProtocolURI destination) {
// do something protocol and resource adaptor specific
// to move the request to another node
}
void processRequestHere(Request request, SessionID sessionID) {
// go to higher layers in the receiving protocol stack
// eventually calling SLEEEndpoint.fireEvent
}
String findLocalOwnerURI(Set<String> ownerURIs) {
// returns the owner URI from the set that looks like an owner URI formatted by this resource adaptor
}
String computeOwnerURI(Request request) {
// returns a string that identifies this node as the owner of the request
}
boolean protocolRecordAndAdoptionResultInSync(SessionOwnershipRecord record, AdoptionResult result) {
// if the record's owner URI's encoded cluster ID and node ID match the AdoptionResult
// then return true - they are in sync
}