Lifecycle extensions are additional stages a component can traverse through during it's lifetime. Lifecycle extensions allow a Container to provide extra functionality to Components in addition to the standard stages defined by Avalon Framework.
Avalon Framework defines a set of standard interfaces often termed as Lifecycle metainfo which tells the ComponentManager how a particular Component should be treated during it's life.
This metainfo allows the developer to separate the various concerns involved when writing a Component, often termed SoC and IoC (Separation of Concerns and Inversion of Control) and is one of primary advantages of using Avalon.
Sometimes it's useful to extend this development paradigm from the framework level into the application domain, to create customized lifecycle extensions that are called upon in addition to the standard set defined by the Avalon Framework.
Such custom lifecycle stages can further enable domain specific logic across many, perhaps even unrelated components, can reduce code duplication, and allows the developer to reuse the same development and thinking paradigm as the standard lifecycle stages.
For example, you might want to pass a specialized SecurityManager to some of your components before they are initialized, or have their internal state persistently cached during system shutdown and restored at during startup. You might want to pass user dependent decryption keys to your component, or give components the opportunity to recycle themselves before being disposed or returned to a pooled component handler.
The possibilities and number of extensions are only limited by the requirements of your particular application domain.
This document describes how to add new lifecycle extensions using Fortress. This document assumes a knowledge of what an Avalon lifecycle is, and a basic understanding of the standard lifecycle interfaces Avalon Framework defines. References in this document to Component and ComponentManager can also be freely interpreted as Service and ServiceManager by the reader.
Support for lifecycle extensions in the other Avalon containers is technically possible but has not yet been discussed. Please check with the Avalon developer mailing list if you use one of these containers and would like to use lifecycle extensions.
Extending a Component's lifecycle is straightforward. An overview of the process follows:
SecurityManageable
,
Cacheable
,
Decryptable
,
Recycleable
interfaces.
LifecycleExtension
, that tests any given
component for the above defined interface (and others if applicable), invoking methods
defined in that interface.
LifecycleExtensionManager
LifecycleExtensionManager
, using either the default manager available inside
of your container, or an externally created manager that is later given to the container
to use.
implements
clause to your Component, or Component implementation,
and write any methods defined in the implemented interface.
lookup()/select()/release()
components as normal
The life of any component can be broken down to the following phases:
lookup()/select()
).
release()
).
A Component will go through it's Creation and Destruction phase only once. Since
ComponentHandler
classes can implement different handling strategies
(Poolable, ThreadSafe, etc), the access and release phases of a component can be
done multiple times.
Lifecycle extensions can be added to any of the above defined phases. This allows you to choose when your particular extension will be executed.
For example, thread or user dependent extensions would be added at the access and release levels (ie. when the component is retrieved and returned to the ComponentManager) as they depend on runtime data not available until they are actually used.
More static, or global extensions would be added at the creation or destruction level, since they do not depend on any external data that change during runtime, nor are they particular to any one context of use.
Support for lifecycle extensions in Fortress is done using the following classes/interfaces.
This interface specifies the business particular extension components will be tested for. It defines the new interface that components will implement to receive additional functionality.
There is no particular base interface the developer needs to extend, and the interface can be kept separate from the Container itself.
Component extensions are invoked via a Lifecycle extension object. Lifecycle extension
objects are managed via a
LifecycleExtensionManager
class and essentially
test for a particular Component extension interface, and appropriately call methods defined
in that interface.
All Lifecycle extension objects must implement the
LifecycleExtension
interface.
The
LifecycleExtension
interface defines 4 methods that are called upon to allow
an implementor to extend a component's lifecycle.
The 4 methods (
create
,
destroy
,
access
and
release
) are invoked by a manager class from inside Fortress. Each method
accepts 2 parameters, the particular Component instance being extended, and the container
Context
.
The container
Context
is passed as a parameter to provide access to any
miscellaneous objects that might be needed during extension code (to make use of this feature
the container Context will need to be initialized with references and passed to the
FortressConfig
during Fortress' startup sequence).
Each method may throw an exception to indicate an error, which will be logged, but will not terminate other extensions from being executed on that Component.
/** * LifecycleExtension interface. This interface defines the methods that * a LifecycleExtensionManager can call on a particular concrete * LifecycleExtensionMarker class. */ public interface LifecycleExtension { /** * Create, called when the given component is being * instantiated. * * @param component a Component instance * @param context a Context instance * @exception Exception if an error occurs */ void create( Object component, Context context ) throws Exception; /** * Destroy, called when the given component is being * decommissioned. * * @param component a Component instance * @param context a Context instance * @exception Exception if an error occurs */ void destroy( Object component, Context context ) throws Exception; /** * Access, called when the given component is being * accessed (ie. via lookup() or select()). * * @param component a Component instance * @param context a Context instance * @exception Exception if an error occurs */ void access( Object component, Context context ) throws Exception; /** * Release, called when the given component is being * released (ie. by a CM or CS). * * @param component a Component instance * @param context a Context instance * @exception Exception if an error occurs */ void release( Object component, Context context ) throws Exception; }
Many extensions will not require implementation of every method defined in the
above interface, for that reason, there's a
AbstractLifecycleExtension
convenience class available which provides default (empty) implementations of each
method which you can extend from. This allows you to implement only the methods
necessary for your particular extension.
The
LifecycleExtensionManager
class provides default management of
extension implementations and an API which ComponentManager/Selector's can call
upon to execute them.
The LifecycleExtensionManager class API is too big to list here, instead please look at the following link. It essentially defines 4 methods for executing extension objects at the various phases of a component's lifecycle, and several methods for registering extension objects with the manager.
The
LifecycleExtensionManager
class will operate safely in multithreaded
environments, and allows you to add/remove extensions to a running system.
By default, all Fortress based containers will be initialized with a default
LifecycleExtensionManager
that contains no extensions. You can alternatively
provide a pre-configured LifecycleExtensionManager to your Container via the
FortressConfig class (
ContainerConstants.EXTENSION_MANAGER
key) if you like.
To add a new lifecycle extension object to the manager simply call the method
LifecycleExtensionManager.addExtension()
. Methods also exist for removing
and iterating through the currently available extensions.
Fortress' inbuilt Component Manager/Selector/Factory code will automatically call upon the LifecycleExtensionManager class at each phase in a Component's life at the following predefined times:
lookup()/select()
.
initialize()
.
dispose()
.
access
and
release
extensions. This is
because the code performing this logic is located in the ComponentManager/Selector classes
(independent from all handlers).
Let's look at a simple example. The following is also available as a working sample in Fortress' examples directory.
Our example implements a Lifecycle extension for passing a
SecurityManager
to
Components. We'll call it the
SecurityManageable
interface.
First we define the new Component extension interface.
/** * Simple custom lifecycle extension interface for supplying a component * with a security manager. */ public interface SecurityManageable { /** * Pass a SecurityManager object to the component * * @param manager a SecurityManager value */ void secure( SecurityManager manager ) throws SecurityException; }
Next we define the actual extension implementation which invokes the
secure()
method. We extend from
AbstractLifecycleExtension
since we only want
secure()
to be invoked upon each access (ie. lookup()) to the component, and
don't need to implement the other 3 LifecycleExtension methods (create, release, and
destroy).
/** * Some custom extensions for this container's components. */ public class Extensions extends AbstractLifecycleExtension { /** * Access, called when the given component is being * accessed (ie. via lookup() or select()). * * @param component a Component instance * @param context a Context instance * @exception Exception if an error occurs */ public void access( Object component, Context context ) throws Exception { if ( component instanceof SecurityManageable ) { // pass in a simple security manager, a real system might want to pass // in specialized/custom security managers ( ( SecurityManageable ) component ).secure( new SecurityManager() ); } } }
An extension class may run components through any given number of extensions, and are not limited to just one.
We then inform our container about the extension. This could be done in several different
ways, for simplicity we'll extend
initialize()
and add it to the
LifecycleExtensionManager
there.
(an alternative might be to initialize a LifecycleExtensionManager before creating the
container and pass it in via the
FortressConfig.setExtensionManager()
method,
or to create a LifecycleExtensionManager subclass that includes the extension preset)
/** * Simple container that includes custom lifecycle extensions. */ public final class ExtendedContainer extends AbstractContainer { public void initialize() throws Exception { super.initialize(); m_extManager.addExtension( new Extensions() ); } }
To use the new SecurityManageable lifecycle extension, we simply implement
SecurityManageable just as we do with any other Avalon lifecycle interfaces
(assuming a predefined Component interface
ExtendedComponent
).
/** * ExtendedComponentImpl, demonstrating the use of a custom * lifecycle stage SecurityManageable. This code does * a simple access check for several files on the file system and logs * the results accordingly. */ public class ExtendedComponentImpl extends AbstractLogEnabled implements ExtendedComponent, SecurityManageable { /** * Pass a SecurityManager object to the component * * @param manager a SecurityManager value */ public void secure( final SecurityManager manager ) throws SecurityException { getLogger().info( "Received SecurityManager instance: " + manager ); final String[] files = { "/tmp", "/vmlinuz", "/usr/lib/libc.a" }; for ( int i = 0; i < files.length; ++i ) { try { manager.checkRead( files[ i ] ); getLogger().info( "Thread can read " + files[ i ] ); } catch ( SecurityException e ) { getLogger().info( "Thread can not read " + files[ i ] ); } } } }