Access Keys:
Skip to content (Access Key - 0)









Hints and Tips for Writing Well Performing SLEE Applications

Print this page

Introduction

This guide introduces good defensive programming practices that will help SLEE application developers avoid many common mistakes during the development of SLEE applications.

A subset of the common performance issues are related to general performance issues in the Java programming language. Remember JAIN SLEE is a Java standard so one must adopt good Java programming practices! This guide will not explore good Java programming guidelines in detail. Readers are encouraged to refer to their favourite Java resource as well as this guide.

There are numerous resources that cover good Java programming guidelines. For example: "Effective Java. Programming Language Guide", by Joshua Bloch.

The rest of this guide will cover topics such as:

  • Tracing
  • Exception handling
  • JNDI
  • Representing state
  • Communication between SBBs
  • Tips for use of Object CMP field types
  • Using Library components

Tracing

SBBs, Resource Adaptors, Profiles, and SLEE internal components can use the Trace Facility to generate trace messages intended for consumption by external management clients. Traces consume resources and could affect application performance if you do not use them properly. We have detected very substantial performance improvements after applying some of the recommendations explained below.

Consider using the Java 5 StringBuilder class to build complex trace messages. The Java 5 javadoc says:
"This class provides an API compatible with StringBuffer, but with no guarantee of synchronization. This class is designed for use as a drop-in replacement for StringBuffer in places where the string buffer was being used by a single thread (as is generally the case). Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations."

So, when developing your SLEE applications, take into account the following suggestions:

  • Calls to the trace facility should be guarded (especially those in your application critical path). Always check that the trace/log level is enabled before calling to trace. This will avoid invoking toString() on events and other objects and lots of String objects as in the following example:
    // ...
        CAPv2ApplyChargingReportReq capAcr = (CAPv2ApplyChargingReportReq) acr; 
        if (traceFine()) {
            final boolean noTarif = capAcr.getTimeInformation().hasTimeIfNoTariffSwitch();
            recordACR("Send: ApplyChargingReportReq", capAcr.getPartyToCharge(),
                                  noTarif ? "noTarifSwitchT" : "tarifSwitchT",
                                  noTarif ? capAcr.getTimeInformation().getTimeIfNoTariffSwitch() 
                                          : capAcr.getTimeInformation().getTimeSinceTariffSwitch(),
                                  capAcr.isCallActive(), capAcr);
        }
    //..
    private void recordACR(String label, final byte toCharge, String timeLabel, final int timeT, 
                           final boolean active, Object acr) {
        StringBuilder sb = new StringBuilder(64);
        sb.append(label).append("[ toCharge=")
                        .append(LegType.CALLED_PARTY == toCharge ? "called party" : "calling party");
        sb.append(' ').append(timeLabel).append("=").append(timeT);
        sb.append(" callActive=").append(active);
        sb.append(" ]");
        trace(sb.toString(), acr);
    }
  • Use the right trace level:
    • Use Info or a higher level for tracing to be collected in the normal case.
    • Use Finest/Finer/Fine level for testing and developing information.
  • Do not trace redundant information.
  • Print data specific of the current entity (for instance, "sendMessage method, state=value"), not about the service logic ("I am in sendMessage").
  • Try to minimize String concatenations in traces and use java.lang.StringBuilder objects for concatenating.
Java uses eager evaluation - all arguments to a method are evaluated before the method itself is executed. This includes calls to the trace facility. Remember that the toString() implementation of objects being traced (such as events) may be computationally expensive!
SLEE 1.1 introduces some enhancements to the trace facility. See Tracing Made Easy in SLEE 1.1

Exception handling

You should follow general Java guidelines related to exception handling:

  • Use exceptions only for exceptional conditions. Avoid using the try/catch blocks as if/else. Only define service logic in a catch block if there is no other possibility and you expect to use that logic in exceptional cases.
  • Do not forget to "clean" resources in the catch block (close connections, remove objects from List,...).
  • Always check that if the service logic should finish in the catch block (return from the method) or it should go on after the try/catch block. Take into account that if you have had an exception in a try block, there could be variables not initialized or not properly updated.
  • Analyse if the exception should be handled in the current method (in a catch block) or thrown to the method invoker.
  • Remember that you can use the finally clause.

JAIN SLEE specification provides two methods to handle exceptional situations sbbExceptionThrown and sbbRolledBack.

sbbExceptionThrown The SLEE invokes this method after a SLEE originated invocation of a transactional method of the SBB object returns by throwing a RuntimeException. Regardeless, take into account that a well-written SBB should not throw any RuntimeException exceptions from any of its SLEE invoked methods.
SLEE 1.1 specification: section 6.9 (SBB abstract class exception call back method), specifically section 6.9.3 (SBB abstract class sbbExceptionThrown method)
sbbRolledBack The SLEE invokes this method after a transaction that was used to invoke a transactional method of the SBB abstract class has been rolled back.
SLEE 1.1 specification: section 6.10 (SBB abstract class and SbbContext interface transaction methods), specifically section 6.10.1 (SBB abstract class sbbRolledBack callback method)

It is important to understand the consequences of allowing a runtime exception to propagate unhandled out of an SBB object

  • for event handler methods invoked by the SLEE or a method invoked via an SBB local interface - or -
  • a profile object via a profile local interface invocation.

An unhandled exception will cause the transaction to be marked for rollback, which can be bad news when you're dealing with non-transactional resources. The SLEE callback methods do alert the SBB to a transaction being rolled back, but there is not a lot you can do about the transactional state that has been lost.

Runtime exceptions shouldn't be thrown by any SLEE invoked method. It also applies to any method invoked via a local interface, although in those cases you don't get the sbbExceptionThrown callback, unless the TransactionRolledbackLocalException propagates out the stack back to the SLEE and then the callback is only performed on the SBB entity invoked by the SLEE in the first instance, which may not be the component that actually threw the exception.

Use runtime exceptions for programming errors and checked exceptions for recoverable conditions. You should not define a runtime exception such as NullPointerException in the throws clause of a profile local interface method to indicate a programming error passing a null argument because you can't throw runtime exceptions from the method in the profile object and expect it to propagate out to the caller as a NullPointerException - it gets wrapped in the TransactionRolledbackLocalException. Use of runtime exceptions within a single method, or between private methods in a single SBB object are generally fine though, but should be handled within the SBB somewhere.

JNDI

SBB configuration data could be stored in environment entries. Each environment entry element binds an environment entry into the JNDI component environment of the SBB. The SLEE implements the SBB component environment, and provides it to the instances of the SBB component classes through the JNDI interfaces at runtime.

The SBB Developer declares in the SBB deployment descriptor all the environment entries that the SBB component expects Service Deployers to customize during Service assembly and deployment. The data stored in environment entries cannot not be changed at runtime: it is necessary to redeploy a Service to change these values. Store data that you only expect to change with a new version of the service.

All instances of SBB component classes of the same SBB component share the same environment entries, so the values of the environment entries can be cached in java attributes safely. A common pattern is to read the SBB jndi environment in setSbbContext() and cache the properties in Java attributes of the SBB object.

/**
 * Initialise the SBB's context
 * @param context
 */
public void setSbbContext(SbbContext context) {
    super.setSbbContext(context);
    try {
        // get the SBB'd jndi context
        Context myEnv = (Context)new InitialContext().lookup("java:comp/env");

        // create and store the ProfileID of a SLEE profile that holds service configuration data
        final String cfgTable = (String) myEnv.lookup("postpaidcharging/cfgtable");
        final String cfgProfile = (String) myEnv.lookup("postpaidcharging/cfgentry");
        cfgProfileID = new ProfileID(cfgTable, cfgProfile);

        // store a reference to the CDR provider object
        cdrProvider = (CDRProvider) myEnv.lookup("slee/resources/cdr/1.1/provider");
    }
    catch (NamingException ne) {
        warn("Lookup failed, unable to initialize SBB", ne);
    }
}

// these are common to all SBB entities that are represented by this SBB object
private ProfileID cfgProfileID;
private CDRProvider cdrProvider;

For more information about this topic read Section 6.13.1 (SBB component environment as a JNDI naming context) in JAIN SLEE 1.1 specification.

Representing Application State

The application persistent state can be stored in:

  • SBB CMP fields
  • Attributes in the Sbb Activity Context Interface of a Null Activity
  • Attributes in the Sbb Activity Context Interface of a Resource Adaptor owned activity
  • Profiles

The use of one or other state storage depends on the life cycle of the state and the components that use this information. This section provides a guidelines that could help you to decide the container to use.

SBB CMP fields

The state should be stored in SBB CMP fields when it is related with the SBB service logic, and its life cycle corresponds to the life cycle of the SBB.

Benefits:

  • There is no additional cost of creating/removing Activities, or memory to store the Activity (only the memory cost of the fields themselves).

Drawbacks:

  • The state can not shared between services.

Attributes in the Sbb Activity Context Interface of a Null Activity

The state should be stored in attributes in the Sbb Activity Context Interface of a Null Activity when the life cycle of the state does not corresponds to the life cycle of the Sbb.

Benefits:

  • The state could be shared between services.

Drawbacks:

  • There is the additional cost of creating/removing an Activity, and memory to store the Activity (and the memory cost of the fields themselves).
  • The Null Activity is bound to a name, so the service developer must make sure that it is removed.

Attributes in the Sbb Activity Context Interface of a Resource Adaptor owned activity

The state should be stored in attributes in the Sbb Activity Context Interface of a Resource Adaptor owned activity when it is related with the Resource Adaptor, and its life cycle corresponds with the life cycle of the Resource Adaptor Activity.

Benefits:

  • The state could be shared between SBBs of different types that use the same activity context (using aliases).
  • There is no additional cost of creating/removing Activities, or memory to store the Activity (only the memory cost of the fields themselves).

Profiles

The application state should be stored in Profiles when it is persistent, i.e. it is not a temporary "state", but new provisioned data that could be required in the future by other SBB entities of the same or different services.

Profiles are objects that contain provisioned data required by a component (e.g. SBB) to perform its function, such as configuration data or per subscriber data. There are two definitions of the Profile Specification, one for SLEE 1.0 and one for SLEE 1.1. This is due to the changes in the Profile contract between 1.0 and 1.1. SLEE components (e.g. SBBs and Resource Adaptors) and management clients can now have write access to 1.1 Profiles (SBBs had a read-only view of Profiles in the SLEE v1.0 specification). Profile updates by SLEE components enable applications to modify Profile data during service execution.

See: New SLEE 1.1 Profile Features for more help using profiles.

Benefits:

  • Changes in profiles are visible for all services.
  • Profile data can be persistent, so it is still available when Rhino SLEE is not running.
  • Profile data can be exported (for backups) and imported (restored or installed in other Rhino SLEE site).

Drawbacks:

  • There is additional cost of profiles persistence (to write them in PostgreSQL database).

Sharing state between SBB of the same type

An SBB may define an SBB Activity Context Interface interface that extends, either directly or indirectly, the generic ActivityContextInterface.

This interface declares the shareable attributes of the SBB and the SLEE generates the concrete class that implements the SBB Activity Context Interface interface when the SBB is deployed into the SLEE.

If an SBB does not define an SBB-specific SBB Activity Context Interface interface, the SBB uses the generic ActivityContextInterface interface. The generic ActivityContextInterface interface does not declare any shareable attributes.

Use the SBB Activity Context Interface interface with care and make sure you do not use ACI attributes when SBB CMP fields should be used instead.

Sharing state between SBB of different types

By default, the Activity Context attributes defined in the SBB Activity Context Interface interface of an SBB are not accessible by other SBBs. The attributes that an SBB entity stores in an Activity Context can only be accessed by SBB entities of the same SBB because SBB entities of the same SBB use the same SBB Activity Context Interface object to interact with the Activity Context.

This avoids unintentional shared access to the same attribute when composing SBBs, or when deploying Services from different sources. This is a common approach in SLEE in general terms - you do not break encapsulation needlessly.

To share the state between SBB of different types, Activity Context attribute aliasing should be used. The aliasing make attributes declared in different SBB Activity Context Interface interfaces behave logically as a single attribute.

It defines a logical attribute with a name known as the alias name. The logical attribute can be updated through any of its aliased attributes' set accessor methods and changes to the logical attribute are observable through any of these attributes' get accessor methods. Shareable attributes defined in an SBB Activity Context Interface interface of an SBB are not 'shared' with other SBBs unless explicitly aliased.

Java Attributes

Be very careful with the use of Java instance variables in your services as their value could change from one transaction to the next transaction, i.e. they are transient. The Java attributes are associated to the SBB object, not to the SBB entity. You can use Java instance variables safely when all instances of the same SBB component share the same instance variables state. For example it is safe to cache values from the jndi environment in Java instance variables if (and only if) the SBB object only ever reads from such instance variables.

The SLEE does not guarantee that all transactions of a specific SBB entity is performed by the same SBB object. The SLEE can choose to disassociate the SBB object from the SBB entity it is currently assigned to. This allows the SBB object to be reused and assigned to a different SBB entity, for example.

To disassociate an SBB object, the SLEE invokes the sbbStore() method to allow the SBB object to prepare itself for the synchronization of the SBB entity's persistent state with the SBB object's transient state. In case you use java attributes, you could use the sbbStore() lifecycle method to synchronize them with CMP fields.

Later, the SLEE can synchronize the transient state held in the SBB object with the persistent state of the SBB entity whenever it determines the need to, by invoking the sbbLoad() method. You could use this method to re-compute or initialize the values of any transient instance variables in the SBB 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.

For more information about sbbLoad() and sbbStore() methods read Sections 6.2 (SBB object life cycle), 6.3.8 (sbbLoad method) and 6.3.9 (sbbStore method) in JAIN SLEE 1.1 specification.

What should be stored in CMP field, ActivityContext attributes and Profile attributes

You should not store large objects in CMP. The SLEE has to use Java serialization on any CMP field that is not a primitive type or string (or array of those types). Java serialization is slow, will write out a lot of extra metadata about the classes being serialized, so the number of bytes to store for each CMP field is quite large. Rhino has to serialize and deserialize this data on every transaction, to make sure the SBB entity state is up to date, so this can use a lot of extra CPU.

Ideally you would use only primitive types in CMP fields, but if you do need to store a more complex object, you can speed up serialization by making the object implement java.io.Externalizable. You then provide your own methods for reading and writing the object, which Java serialization will call instead of it's default serialization code, so you can write the object more efficiently. See the javadoc for java.io.Externalizable.

For example, imagine you have a class that contains several maps and lists. By default, Java serialization will include metadata about the map and list classes, as well as their contents. If you implement Externalizable, you can store a list more efficiently by just writing the list size, and then writing each list item. When you deserialize, just read the size, then instantiate your list object and add the items read from the stream. Apply this to all the classes you are storing in CMP.

Another possibility to use persistent complex objects is dividing them in primitive persistent fields and storing its internal elements in CMP fields. When starting a new transaction, using sbbLoad() method (explained above), you could create a new complex object using its internal elements. Later, when the transaction finishes, the complex object could be stored again in primitive CMP fields in the sbbStore() method.

Tips for Object CMP field types

Object CMP field types are types passed to CMP that are not either Java primitive types or their associated Object wrappers.
An example of a Java primitive type and its Object wrapper is boolean and java.lang.Boolean.

By default, Rhino will use Java Serialization in order to store such Objects in CMP. This is extremely CPU and memory inefficient. Therefore in addition to using a lot of CPU, Rhino will allocate and then immediately discard object references, meaning that Garbage Generation is much higher than needed. This in turn impacts latency and throughput.

OpenCloud recommends two alternatives to Java Serialization, these are discussed below.
Finally OpenCloud recommends use of Object field CMP caching in addition to these faster alternatives to standard Serialization.

Use of OpenCloud's FastSerializable

OpenCloud implements a proprietary mechanism called FastSerializable. As the name implies it is similar to Serializable, but its key point is that it is fast. It is a little more manual, but is still relatively simple.

There are two parts to the contract. The first is the Object properly implementing FastSerializable.
The second is the CMP Field accessor requirements.

The contract for FastSerializable is shown below.

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import com.opencloud.util.FastSerializable;

// ... other code

// the class must be public and must implement FastSerializable
public class MyClass implements FastSerializable {

    private boolean flag;
    private int counter;

    // the class must implement toStream taking a DataOutput as an arg
    // it should propagate any IOExceptions when writing itself out
    public void toStream(DataOutput out) throws IOException {
        out.writeBoolean(flag);
        out.writeInt(counter);
    }

    // the class must implement a public constructor taking a DataInput as the only arg
    // it should propagate any IOExceptions when reading itself in
    public MyClass(DataInput in) throws IOException {
        flag = in.readBoolean();
        counter = in.readInt();
    }

    // ... other code
}

The CMP Field accessor requirements are that:

  • The CMP Field accessors are for a specific type
  • They should not be for Object or for FastSerializable

Example CMP Field accessors for our example class "MyClass" are as follows:

public abstract void setMyClass(MyClass object);
public abstract MyClass getMyClass();

Use of CGIN's Persist API

For CGIN use the CGIN Persist API. This allows the type to be turned to and from a byte array with very fast encoding and decoding.
Latency and throughput are significantly enhanced.

This is more specific and faster than FastSerializable and is automatically present in the CGIN API.
CGIN Persist offers encoder/decoder classes for every single CGIN Operation "Arg" type.
So this is true for all MAP Operations, all CAP Operations, all INAP Operations and so on.

Below is a code snippet that uses CAPv2 and encodes an InitialDP Arg. The same approach is possible with MAP, just use a
different codec import, and use the appropriate "Arg" object.

The CAPv2 specific example looks like this:

import com.opencloud.slee.resources.cgin.cap_v2.persist.CAP2Codecs;

// other code here ....

private byte[] encodeInitialDP(CCInitialDPArg initialDP) {
    return CAP2Codecs.CAP_DataTypes_InitialDPArg.toByteArray(initialDP);
}

private CCInitialDPArg decodeInitialDP(byte[] encoded) {
    return CAP2Codecs.CAP_DataTypes_InitialDPArg.fromByteArray(encoded);
}

Caching of Object CMP fields

JSLEE CMP fields are pass-by-value. This means that when you invoke the getXXX() or setXXX(arg) accessor operations that the SLEE is taking a copy of the argument and using that copy.

As JSLEE and Rhino have Object pools in place for SBB entities, you can set the Ready Pool Size to be larger than the expected number of SBB entities.
Therefore the "copy" semantics of JSLEE CMP fields will be used infrequently, as the Object pool means that the Object form can be readily used without resorting to the underlying CMP field.

This mechanism works by using a pattern where:

  • There is a CMP field accessor pair
  • There is a boolean attribute of the SBB Abstract Class called 'dirty'
  • There is an Object attribute of the SBB Abstract Class which is the "cached object" for the CMP field
  • sbbLoad, sbbPassivate, and sbbStore operations are required to be implemented
  • There are a pair of "higher level accessor" operations that manage the dirty flag, the cached object, and the actual CMP accessors themselves

Below is a snippet from one of our applications that needs to store an InitialDP Arg into CMP. This is also cached.

The SBB has two class fields to store the decoded form:

private CCInitialDPArg initialDPCache;
private boolean initialDPCacheDirty;

It has a CMP field to store the encoded form:

/**
 * @slee.cmp-method persisted version of the InitialDP received
 */
public abstract byte[] getPersistedInitialDP();
/**
 * @slee.cmp-method persisted version of the InitialDP received
 */
public abstract void setPersistedInitialDP(byte[] encoded);

Implement sbbStore() and sbbPassivate() as follows (delete the super.sbbXYZ() if not appropriate):

@Override
public void sbbStore() {
    super.sbbStore();
    if (initialDPCacheDirty) {
        setPersistedInitialDP(encodeInitialDP(initialDPCache));
        initialDPCacheDirty = false;
    }
}

@Override
public void sbbPassivate() {
    super.sbbPassivate();
    initialDPCache = null;
    initialDPCacheDirty = false;
}

Implement sbbLoad() as the CMP representation has changed (this is why sbbLoad exists).

@Override
public void sbbLoad() {
    super.sbbLoad();
    // cache is invalid
    initialDPCache = null;
    initialDPCacheDirty = false;
}

Add getter & setter for the InitialDP arg. You use these methods in application code rather than accessing the CMP field directly:

public void setInitialDP(CCInitialDPArg initialDP) {
    initialDPCache = initialDP;
    initialDPCacheDirty = true;
}

public CCInitialDPArg getInitialDP() {
    if (initialDPCache != null) return initialDPCache;

    byte[] data = getPersistedInitialDP();
    initialDPCache = decodeInitialDP(data);
    initialDPCacheDirty = false;
    return initialDPCache;
}

Now because the type here is a CGIN Arg type ... we use CGIN Fast Persist to perform the encoding and decoding operations.

This is specific to CGIN Arg types, so the pattern described above works will cache any type.

Tip for writing encode and decode methods

The CGIN encode and decode methods return null if their argument is null.
This simplifies the calling code.

If you need to write encode and decode methods for objects (e.g. they do not implement FastSerializable) then
follow the pattern where they return null if a null argument is passed to them (rather than throwing a NullPointerException).

Communication between SBBs

SSBs can communicate between themselves using the SBB Local Interface or by sending Custom Events. This section provides guidelines that could help you to decide how to implement such communication.

SBB Local Interface

The SBB Local Interface allows an SBB entity to invoke synchronously a method in another SBB entity (i.e. a root SBB can invoke a method on a child SBB, and viceversa).

The methods that may be invoked synchronously must be declared in the SBB Local Interface, and must be implemented in the SBB abstract class. The methods of an SBB Local Interface execute in the calling methods transaction context, so they do not involve a new transaction.

The Local Interface does not allow SBBs of different services to communicate. Use SBB Local interfaces when the SBBs are involved in a whole-part relation (i.e parent-child in SLEE terms). For example, a parent SBB may delegate to a helper child SBB.

Use-Case
Call Forwarding service. The parent SBB could be in charge of handling the call flow and the child SBB could access to an external database to provide the diverts configured for an specific destination number. They are not independent services, because the child is useless without a call handler and does not provide a complete service.

For more information about this topic read Chapter 5 (SBB Local Interface) in the JAIN SLEE 1.1 specification.

Custom Events

Custom Events allow SBBs to communicate with SBBs of the same or other services.

The SBB abstract class must define fire event methods, that may be invoked to fire an event that will be processed asynchronously. The SLEE stores the event internally as part of its internal transaction processing and will be delivered to other SBB entities at some later time if the transaction successfully commits. If the transaction does not commit successfully the SLEE discards the event.

Each event handler method invoked to process an event fired asynchronously executes within a separate transaction.

As other SBB entities will receive the fired event after the originating transaction has committed, transactional state changes made by the originating transaction are visible to subsequent event handler method invocations, e.g. state held within an Activity Context.

There are some important considerations:

  • The event firing semantic of an SBB is 'fire and forget' - that is the SBB does not receive any notification that any SBBs have processed the event
  • Custom events should only be used for SBBs that are not in a whole-part relation, i.e, the SBBs are in completely independent services
  • Custom events consume more resources than Local Interface communication (more transactions and events), so check that you can not use Local Interface instead before using custom events.

For more information about this topic read section 3.2 (Custom event types) in the JAIN SLEE 1.1 specification.

Common uses of Null Activities

Null Activities are a special type of activity:

  • Internal to the SLEE
  • Not associated with any external resource (the SLEE is the resource)
  • Can be but created on demand by SBBs

Service developers starting to develop JSLEE applications, commonly use Null Activities too frequently. Null Activities consume CPU resources of creating/removing an Activity, and the memory to store the Activity, so they should only be used when they are necessary.

This section provides some situations that could be implemented using Null Activities.

Sharing state between SBB Entities

Null Activities could be used to share state between SBB entities via AC Naming and/or shared ACI attributes.

Use-Case
There is an SBB1 (that belongs to Service1) that processes calls and stores the call duration for each subscriber in a Null Activity whose name is the originator address. Another SBB2 (that belong to Service2) recollects periodically the call duration for statistical purposes of each originator address.

To implement this scenario with Null Activities we have to take into account:

  • The Null Activity should be bound to a name that both SBBs know (e.g. the originator address).
  • Once an SBB finds the activity (via the lookup() method), the SBB could attach to it and will be able to use the Null Activity again (to update its attributes, to set a Timer,...) via the sbbContext.getActivities() method, instead of the lookup().
  • It is responsibility of the services to make sure that the Null Activity is ended when it is not needed any more (unbinding it or calling to endActivity() method)

In SLEE 1.1 containers, profiles can also be used to implement this use case.

Firing custom events between SBBs

Null Activities could also be used to communicate between SBB entities using custom events.

In a common scenario we could have two services with two SBBs (SBB-1 and SBB-2) that have to exchange messages with some information. For instance, SBB-1 sends custom event Event2 to SBB2 and waits for custom event Event-1 from SBB-2. The SBB-2 initial event could be Event-2, and it fires Event-1 as a response of Event-2.

The right way to implement this logic would be:

  1. SBB-1 creates a Null Activity.
  2. SBB-1 attaches to the Null Activity.
  3. SBB-1 fires Event-2 on the Null Activity.
  4. Event-2 is an initial event for SBB-2. The SLEE creates a new SBB entity and ...
  5. ... attaches it to the Null Activity (as a part of normal SLEE initial event processing).
  6. SBB-2 method onEvent2() is executed.
  7. SBB-2 fires Event-1 on the Null Activity
  8. SBB-2 detaches from the Null Activity
  9. SBB-1 onEvent1() method is executed
  10. SBB-1 detaches from the Null Activity
  11. Null Activity is garbage collected by the SLEE
One common mistake is for a developer to forget to attach SBB-1 to the Null Activity (step 2), so SBB-1 never receives the response Event-1 event (steps 9 and 10 are never executed).

Keeping an SBB Entity alive

During normal Rhino SLEE operation SBB entities and Activities are removed by the SLEE when they are no longer needed. For instance, SBB entities are removed when all activities attached to the SBB have ended.

Null Activities could be created to keep the SBB entity alive when not attached to other Activity Context Interface interfaces. The SLEE will not remove the SBB Entity if its attachment count is higher that zero.

A common use case is chain independent activities together that really are related. For instance, in a messaging scenario we may want the same SBB entity tree to process the message and the corresponding delivery receipt (both of which are related to separate activities).

In this scenario the SBB entity receives a DeliveryRequest and sends a DeliveryResponse and a SubmitRequest. When the SBB entity receives the SubmitResponse, it creates a Null Activity and attaches to it to keep alive. The Activity of the Resource Adaptor finishes after the SubmitResponse, but SLEE will not remove an SBB Entity because the attachment count is higher than zero.

When the DeliveryReportRequest is received, we will use the initial event selector method to check if the requests belong to the same "flow of messages". In that case the message is sent to the same SBB entity: the SLEE does not create a new SBB entity because one already exist for the potential initial event.

The SBB entity sends a DeliveryReportResponse, detaches from the Null Activity and calls to the endActivity() method.

For more information about this topic read section 7.10 ("Null Activity objects") in JAIN SLEE 1.1 specification.

Library components

WHAT are library components?

JAIN SLEE 1.1 Specification reads:

A library is a SLEE component that provides some common functionality to other components installed in the SLEE. A library can depend on other libraries in the SLEE, but cannot depend on other types of components such as SBBs and Resource Adaptors. This means that a library is typically a "passive" component used by other components to support their functionality, rather than an "active" component which initiates interaction with other components in the SLEE.

WHEN should library components be used?

Libraries should be used to:

  • Package common classes used in several services, resource adaptors, events, etc.
  • Package a library built by other vendor (for instance, a LDAP library or JAIN SIP libraries) that should be managed independently.

WHY should library components be used?

There are two main reasons for using libraries:

  • It makes the application easier to manage and support.
  • It allows another level of reuse! SBBs, Resource Adaptors, etc. could share common classes.

HOW are library components used?

Libraries components are a new feature of JAIN SLEE 1.1. Rhino 2.0 implements JAIN SLEE 1.1, so you can use standard library components.

See: How Do I Use Library Jars in SLEE 1.1 for more help using libraries.

Before Rhino 2.0, you can use libraries using proprietary deployment descriptors. It is trivial to migrate these deployment descriptor to the standard ones if you have to migrate an application from JAIN SLEE 1.0 to 1.1.

The first step is to define an oc-library-jar.xml file. It would look something like this:

<?xml version="1.0"?>
<!DOCTYPE library-jar
    PUBLIC "-//Open Cloud Ltd.//DTD JAIN SLEE Library Extension 1.0//EN"
    "http://www.opencloud.com/dtd/slee-library_1_0.dtd">
<library-jar>
      <library>
          <library-name> ... </library-name>
          <library-vendor> ... </library-vendor>
          <library-version> ... </library-version>
          <jar>
              <jar-name>abc.jar</jar-name>
          </jar>
          <jar>
              <jar-name>xyz.jar</jar-name>
          </jar>
      </library>
</library-jar>

Once you've created the library component jar, it can be deployed in Rhino by adding it to any deployable unit in the usual way.

Library components need to be referenced by other components in order to be used. Again we need to use extension deployment descriptor to support this currently in Rhino. You can reference a library from other SLEE components by using extension deployment descriptors as below:

  • For event jars: META-INF/oc-event-jar.xml
  • For profile specs: META-INF/oc-profile-spec-jar.xml
  • For resource adaptor types: META-INF/oc-resource-adaptor-type-jar.xml
  • For resource adaptors: META-INF/oc-resource-adaptor-jar.xml
  • For SBBs: META-INF/oc-sbb-jar.xml

To use the extension deployment descriptors, you need to use the "id" attributes in the SLEE-defined deployment descriptors to link the two together. As an example, say you want to implement an SBB that uses a library. Your sbb-jar.xml would look something like this:

<?xml version="1.0"?>
<!DOCTYPE ... >
<sbb-jar>
    <sbb id="mysbb">
       ...
    </sbb>
</sbb-jar>

And your oc-sbb-jar.xml would look like this:

<?xml version="1.0"?>
<!DOCTYPE oc-sbb-jar PUBLIC
    "-//Open Cloud Ltd.//DTD JAIN SLEE SBB Extension 1.0//EN"
    "http://www.opencloud.scom/dtd/oc-sbb-jar_1_0.dtd">
<ob-sbb-jar>
    <sbb id="mysbb">
      <library-ref>
        <library-name> ... </library-name>
        <library-vendor> ... </library-vendor>
        <library-version> ... </library-version>
      </library-ref>
    </sbb>
</ob-sbb-jar>

You would include both sbb-jar.xml and oc-sbb-jar.xml in the META-INF directory of your SBB component jar.

You can find the DTDs for all the extension deployment descriptors mentioned above in the com/opencloud/rhino/dtd directory inside the lib/RhinoSDKRuntime.jar or lib/RhinoRuntime.jar file included in the Rhino SDK install or the Rhino install.

You can also grant additional security permissions in the oc-library-jar.xml deployment descriptor. For example, to allow a library to establish a socket connection to another host you would include the following in the deployment descriptor:

<security-permission>
    <security-permission-spec>
      grant {
        permission java.net.SocketPermission "*", "connect,resolve";
      };
    </security-permission-spec>
</security-permission>

You can specify different security permissions for each individual jar included in a library component.

Applying additional security permissions to a non-SBB component is usually only useful with the use of AccessController.doPrivileged() blocks in the appropriate places in the code. See the class javadoc for java.security.AccessController for more details.

  1. October 17, 2012

    fabrizio cannizzo says:

    With regards to logging, three other tips which may help improving performance: ...

    With regards to logging, three other tips which may help improving performance:

    • where possible apps should migrate to slf4j, which offers a "pametrised logging" api that removes the needs of guarding the log statements.
    • i believe Sun's compiler compiles String.concat() (eg +) to StringBuffer. So for the sake of clarity, small messages can still be constructed using string concatenation.
    • for long (^) messages it should be better to use StringBuffer and construct it with a capacity slightly over the size of the result log string (if it can be reliably calculated at compile time). The Sun implementation of StringBuffer expands the internal capacity of the internal buffer by duplicating it's size and performing an Array.copyOf() which obviously can be avoided if the capacity is chosen wisely in the first place.

    (^) long message: looking again at Sun's implementation a message can be considered long if it's length is > 16 chars, as 16 is the default size of a StringBuffer.

Adaptavist Theme Builder Powered by Atlassian Confluence