All,

Avalon has traditionally had the same client semantics for all 
supported component lifestyles. So no matter if the component was
a singleton or one instance from a pool, the client code would
look the same.

It is my understanding that the original client code looked like
this:

    MyComponent comp;

    public void compose (ComponentManager manager) {
        comp = manager.lookup (MyComponent.ROLE);
    }

    public void dispose () {
        manager.release (comp);
    }

    public void doStuff () {
        comp.doStuff ();
    }

So you had one lookup in compose(), and a release in dispose().
All components were ThreadSafe/Singleton. Phoenix coders didn't
even bother with the release(), letting the container simply
invalidate the component when the block was shut down.

Then came Cocoon, and a need for pooled components. The client
code could no longer share one component instance among all 
threads, but had to get a unique instance to itself. This
led to the now-standard example of using a SAXTransformer 
component to illustrate a component interface that requires pooling.

Since some components could be pooled, the client code therefore 
became:

    ComponentManager manager;

    public void compose (ComponentManager manager) {
        this.manager = manager;
    }

    public void dispose () {
    }

    public void doStuff () {
        MyComponent comp = null;
        try {
            comp = manager.lookup (MyComponent.ROLE);
            comp.doStuff ();
        } catch (ComponentException ce) {
            // -- ignore
        } finally {
            manager.release (component);
        }
    }

That's a lot of tries, lookups, catches, finallies and releases.

We have from time to time tried to get away from all this lookupping
and releasing, but the attempts have always stalled on the following:

  + Pooled components must look the same as Singleton components from
    the client's POV.

  + Components that maintain an internal per-client state (think 
    SAXTransformer) must be supported.

I'll refer to the first type of components as "pooled" and the other
type
as "stateful". Some components are pooled and some are both pooled and 
stateful. And they all mess up the client contract. (No component is
just stateful, since this would imply that it only could handle one
client.)


                              - o O o -

A slight detour.

When using "Constructor Dependency Injection" / IoC type 3, all
dependencies are given in the constructor:

    private final MyComponent comp;

    public MyClient (MyComponent comp) {
        this.comp = comp;
    }

The client can then hold on to the reference for its entire life.
This is what we'd like to achieve, but those pooled maybe stateful
components stop us.

                              - o O o -

What I propose here is to accept different client contracts for
stateful and stateless components. We have three component interfaces:

    StatelessSingleton - this is a stateless singleton component. Think:

        interface MailServer {
            public void sendEmail (String recipient, String message);
        }
    
    StatelessPooled - this is a stateless pooled component. Think:

        interface DBConnection {
            public void executeQuery (String query);
        }
    
    StatefulPooled - this is a stateful pooled component. Think:

        interface OutputStream {
            /** must be called first. */
            public void open ();

            /** open must be called first. */
            public void write (String text);

            /** must be called last, and open must be called 
                first. */
            public void close ();
        }
    

The StatelessSingleton type is easy to handle - it is already handled
by IoC type 3 and the original Avalon code. The question now is - can we
make the StatelessPooled and StatefulPooled interfaces appear as if they
were StatelessSingleton:s? Well, almost.

StatelessPooled can be made to look like a StatelessSingleton if we
proxy 
it. Then, every method call is turned into a:

   1. get an instance from the pool
   2. do the method call.
   3. put the instance back into the pool

This is no problem - maybe a bit inefficient, but hey, I bet we can
optimize some stuff away, and Stateless interfaces tend to do a lot
in each method call, so the time to lookup and release will be very
short compared to the time it takes to execute the method call:

   1. get an instance from the pool (1ms)
   2. do the method call. (5000ms)
   3. put the instance back into the pool (1ms)


StatefulPooled interfaces are the black sheep of Avalon. Simply put,
we *can't* turn them into StatelessSingleton-lookalikes. Thus, we
must force some responsibility on to the client. This isn't as hard
as it may seem at first - when the client deals with stateful as opposed
to stateless interfaces, the client code tends to be forced to be aware
of the statefulness anyhow. Thus, we're not really adding an entirely
new requirement here - and the potential reward is a great
simplification
of the client API, enabling constructor dep. injection, autoinjection 
based on attributes, XStream deserialization, or whatever.

Therefore I will introduce a new interface:

    interface TransientFactory {
        public Object get ();
        public void   put (Object o);
    }

All stateful components will be exposed to the client via
this interface. When you do a lookup on a stateful component, you
don't get a component, instead you get a TransientFactory.

The workings of the factory should be obvious.

This means that when doing IoC type 3, you get:

    private final TransientFactory saxTransformerFactory;

    public MyClient (TransientFactory saxTransformerFactory) {
        this.saxTransformerFactory = saxTransformerFactory;
    }

    public void process () {
        SAXTransformer tf = 
            (SAXTransformer) saxTransformerFactory.get ();
        try {
            tf.startDocument ();
            ...
            tf.endDocument ();
        } finally {
            saxTransformerFactory.put (tf);
        }
    }

Since the type of pooled object isn't present in the above example,
you may need some extra metadata:

    /**
     * @@.saxTransformerFactory Dependency (SAXTransformer.class)
     */
    public MyClient (TransientFactory saxTransformerFactory) {
        this.saxTransformerFactory = saxTransformerFactory;
    }

To make sure the container knows that the TransientFactory passed 
in as saxTransformerFactory should produce SAXTransformers and not
something else.

                              - o O o -

This is one for A5 definitely, but I kinda like it and may try it
out by myself.

/LS


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to