avalon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Marcus Crafter <craft...@fztig938.bank.dresdner.net>
Subject Lifecycle extensions support added
Date Tue, 09 Jul 2002 13:19:50 GMT
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:crafterm@apache.org">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:crafterm@apache.org">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:bloritsch@apache.org">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:crafterm@apache.org">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:crafterm@apache.org">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:crafterm@apache.org">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:avalon-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:avalon-dev-help@jakarta.apache.org>


Mime
View raw message