Hi All,
Hope all is well.
Over the past couple of weeks I've been working on providing
support for adding custom lifecycle extensions to containers.
The basic idea is to provide a standard way for extending the
component lifecycle, without requiring excessive subclassing of
handlers, or containers.
First, to summarize what's previously been discussed:
<summary>
A few weeks back I sent in a proposal for 'custom markers' which
sparked several discussions about how to customize/extend the
lifecycle of a component.
Based on the discussions I examined the following implementation ideas
(the following examples use Fortress as the base container).
1. Extend lifecycle via a custom component handler/factory:
eg. in the roles file:
<role name="org.apache.avalon.excalibur.xml.Parser">
<component shorthand="parser"
class="org.apache.avalon.excalibur.xml.JaxpParser"
handler="package.MyComponentHandler"/>
</role>
This is easy to implement, and doesn't require container
modification. The component handler acts as a 'sub-container', and
provides all extensions via the defined
ComponentHandler/Factory interfaces (get,put,newInstance,etc).
The main problem I find with this approach is that it mixes 2
concerns -> extensions code, and component handler code. If you want
to put the same extension code in a different handler, code has to be
copied and/or a handler has to be subclassed.
2. Extend lifecycle via a custom container:
eg. (using Fortress's AbstractContainer as a base)
public class MyContainer extends AbstractContainer
{
protected ComponentManager getComponentManager()
{
return new MyComponentManager(
super.getComponentManager()
);
}
// My ComponentManager providing extensions
private class MyComponentManager implements ComponentManager
{
public MyComponentManager(ComponentManager cm)
{
m_parent = cm;
}
public Component lookup(String role) throws ...
{
Component comp = m_parent.lookup(role);
// Extensions code here, for example
if (comp instanceof SomeInterface)
{
((SomeInterface) comp.someMethod();
}
if (comp instanceof AnotherInterface)
{
((AnotherInterface) comp.otherMethod();
}
}
public void release(Component component)
{
...
}
...
}
}
(TIMTOWTDI I'm sure (Perl-speak))
This approach also doesn't require modification of the container
foundations (but it is tied to the base container implementation
used, if any). It also doesn't cover ComponentSelectors, which also
need to be wrapped.
The main problem I see with this solution is not so much
at the technical level, but that it promotes a proliferation of
container subclasses to add lifecycle functionality. The problem
manifests when a concrete container is already chosen in an existing
system, like Cocoon for example. Merging container hierarchies
then becomes a difficult task.
3. Extend lifecycle by container modification.
This is where the container is modified to support lifecycle
extensions via callbacks to an extension class.
The idea is to be able to get a container and register lifecycle
extensions with it, which will then be checked for and executed at
runtime, during the various phases of a components lifetime.
No container or handler subclassing is required, extensions are
inserted at compile time or runtime when they are needed. The
drawback, container modification is obviously necessary. Components
that use this functionality are also tied to containers that only
support lifecycle extensions.
</summary>
After several discussions with people on and off the list I
decided to continue examining the 3rd option.
The basis for this decision came down to the requirement of not
having to create excessive subclasses of either handlers or
containers to extend the lifecycle.
What I really wanted to provide was a solution that could be taken
and used in a system, but one that also allowed that system to be
extended without having to make architectural changes or change high
level classes.
Unfortunately, options 1 and 2 require subclassing so after much
thought I discounted them.
I realize that several people on the list feel that container
modification is not the way to go. I do recognize and have thought
about this, and would really like to find a way to make both camps
happy.
It would be great to get feedback about the various approaches to
come up with an ideal solution that's workable, portable, and usable
across many containers and application domains. I'm more than
happy to make changes to make things better. I hope this is the
right-thing-to-do (TM).
So, here's a run-down of the current solution:
The aim is to make it possible to extend a component's lifecycle
in the following areas if its lifetime:
1. access (when its accessed via lookup())
2. release (when its released via release())
3. creation (when the component is instantiated)
4. destruction (when the component is decomissioned)
This allows one to create extensions that are executed on a
component once when the component is actually instantiated or
disposed, ready for GC - or potentially multiple times when a
component is accessed and released.
The following LifecycleExtension interface is defined:
/**
* <code>LifecycleExtension</code> interface. This interface defines the methods that
* a <code>LifecycleExtensionManager</code> can call on a particular concrete
* <code>LifecycleExtensionMarker</code> class.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2002/05/13 12:17:39 $
*/
public interface LifecycleExtension
{
/**
* Create, called when the given component is being
* instantiated.
*
* @param component a <code>Component</code> instance
* @param context a <code>Context</code> instance
* @exception Exception if an error occurs
*/
void create( Object component, Context context )
throws Exception;
/**
* Destroy, called when the given component is being
* decomissioned.
*
* @param component a <code>Component</code> instance
* @param context a <code>Context</code> 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 <code>Component</code> instance
* @param context a <code>Context</code> 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 <code>Component</code> instance
* @param context a <code>Context</code> instance
* @exception Exception if an error occurs
*/
void release( Object component, Context context )
throws Exception;
}
where each method reflects an extension to a particular phase of a
components lifecycle.
Concrete extension classes then implement the methods they see fit,
and are then registered with a LifecyceExtensionManager class,
which the containers component manager calls upon at the appropriate
times in a components life.
More than one extension object can be registered with the extension
manager, and more than one lifecycle extension can be performed by
any particular extension object as well.
The container context is also passed to each extension method to
allow the developer to pass other objects around that may
influence how the particular extension works.
The LifecycleExtensionManager class looks like:
/**
* <code>LifecycleExtensionManager</code> class. This class manages lists
* of extensions objects that are executed on components during the various
* stages of their lifecycles.
*
* <p>
* It provides 4 methods for adding extension objects to the system,
* and 4 methods for executing them on a particular component object. The
* current context is also passed in to the extension objects to facilitate
* the communication of any global values.
* </p>
*
* @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2002/05/13 12:17:39 $
*/
public class LifecycleExtensionManager
extends AbstractLifecycleExtensionManager
{
// extensions objects
private final List m_accessExtensions = new ArrayList();
...
/**
* <code>executeAccessExtensions</code> method, executes all <i>access</i>
* level extensions on the given component.
*
* @param component a <code>Component</code> instance
* @param context a <code>Context</code> instance
* @exception Exception if an error occurs
*/
public void executeAccessExtensions( Object component, Context context )
throws Exception
{
executeExtensions( m_accessExtensions.toArray(), component, context, ACCESS );
}
/**
* Obtains the access level lifecycle extension this manager manages.
*
* @return a <code>List</code> of extensions
*/
public List getAccessLifecycleExtensions()
{
return m_accessExtensions;
}
...
}
(I've just shown the 'access' methods, there's also methods for
each of the other phases in a components lifetime).
This class is then used FortressComponent/ServiceManager as follows:
/**
* This is the Default ServiceManager for the Container. It provides
* a very simple abstraction, and makes it easy for the Container to manage
* the references.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Berin Loritsch</a>
* @version CVS $Revision: 1.3 $ $Date: 2002/07/09 10:40:26 $
*/
public class FortressServiceManager implements ServiceManager
{
...
public Object lookup( String role )
throws ServiceException
{
...
try
{
if( !handler.isInitialized() )
{
handler.initialize();
}
component = handler.get();
>>> m_extManager.executeAccessExtensions( component, m_context );
}
...
return component;
}
public void release( Object component )
{
final ComponentHandler handler;
try
{
>>> m_extManager.executeReleaseExtensions( component, m_context );
}
...
}
}
and also similarly in the ComponentFactory class.
Here's an example that implements a SecurityManageable extension:
First the lifecycle extension interface:
/**
* Simple custom lifecycle extension interface for supplying a component
* with a security manager.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2002/05/13 12:17:39 $
*/
public interface SecurityManageable
{
/**
* Pass a SecurityManager object to the component
*
* @param manager a <code>SecurityManager</code> value
*/
void secure( SecurityManager manager )
throws SecurityException;
}
Then the extensions class that is registered with the container.
/**
* Some custom extensions for this container's components.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2002/05/13 12:17:39 $
*/
public class Extensions
extends AbstractLifecycleExtension
{
/**
* Access, called when the given component is being
* accessed (ie. via lookup() or select()).
*
* @param component a <code>Component</code> instance
* @param context a <code>Context</code> 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 for testing, a real
// system might want to pass in specialized/custom security managers
( ( SecurityManageable ) component ).secure( new SecurityManager() );
}
}
}
And an example component that uses it:
/**
* <code>TestComponentImpl</code>, demonstrating the use of a custom
* lifecycle stage <code>SecurityManageable</code>. This code does
* a simple access check for several files on the file system and logs
* the results accordingly.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2002/05/13 12:17:39 $
*/
public class ExtendedComponentImpl
extends AbstractLogEnabled
implements ExtendedComponent, SecurityManageable
{
/**
* Pass a SecurityManager object to the component
*
* @param manager a <code>SecurityManager</code> value
*/
public void secure( final SecurityManager manager )
throws SecurityException
{
getLogger().debug( "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( "Object can read " + files[ i ] );
}
catch ( SecurityException e )
{
getLogger().info( "Object can not read " + files[ i ] );
}
}
}
}
Simple example, but I hope you get the idea.
I'm in the process of checking the code into Fortress, and will also
be checking in the above example into the
jakarta-avalon-excalibur/fortress/examples directory.
Once the code is checked in, I'm more than open to suggestions, and
feedback, so please feel free to fire away with any questions
(hopefully I or someone else can answer them all!) :)
Finally a special thanks to Berin and Leo Simons for their help! :)
Cheers,
Marcus
--
.....
,,$$$$$$$$$, Marcus Crafter
;$' '$$$$: Computer Systems Engineer
$: $$$$: ManageSoft GmbH
$ o_)$$$: 82-84 Mainzer Landstrasse
;$, _/\ &&:' 60327 Frankfurt Germany
' /( &&&
\_&&&&'
&&&&.
&&&&&&&:
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>