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.
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.
"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."
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!
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.
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.
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.
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.
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.
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.
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.
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.
There is no additional cost of creating/removing Activities, or memory to store the Activity (only the memory cost of the fields themselves).
The state can not shared between services.
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.
The state could be shared between services.
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.
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.
The state could be shared between SBBs of different types that use the same activity context (using aliases).
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.
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).
There is additional cost of profiles persistence (to write them in PostgreSQL database).
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.
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.
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.
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.
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.
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.
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.
different codec import, and use the appropriate "Arg" object.
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.
Below is a snippet from one of our applications that needs to store an InitialDP Arg into CMP. This is also cached.
Implement sbbLoad() as the CMP representation has changed (this is why sbbLoad exists).
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.
The CGIN encode and decode methods return null if their argument is null.
This simplifies the calling code.
follow the pattern where they return null if a null argument is passed to them (rather than throwing a NullPointerException).
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.
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.
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 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.
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.
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.
Null Activities could be used to share state between SBB entities via AC Naming and/or shared ACI attributes.
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.
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().
In SLEE 1.1 containers, profiles can also be used to implement this use case.
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.
SBB-1 creates a Null Activity.
SBB-1 attaches to the Null Activity.
SBB-1 fires Event-2 on the Null Activity.
... attaches it to the Null Activity (as a part of normal SLEE initial event processing).
SBB-2 method onEvent2() is executed.
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).
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.
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?
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?
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.
PUBLIC "-//Open Cloud Ltd.//DTD JAIN SLEE Library Extension 1.0//EN"
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.
"-//Open Cloud Ltd.//DTD JAIN SLEE SBB Extension 1.0//EN"
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 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.
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.