You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avalon.apache.org by Robert Mouat <ro...@mouat.net> on 2002/06/17 21:19:09 UTC
[A5 proposal] Sessions
This proposal is inspired by postings by Berin Loritsch...
==== goal of Sessions ====
As I understand it, the purpose of sessions is to allow an initialized
component to be used by multiple clients by associating a session object
with each component lookup requested by a client.
Requirements of a session implementation:
- It should be completely transparent the client.
- The component should not be responsible for any session management.
==== proposal (general overview) ====
SessionEnabled: a framework lifecycle interface telling the
container+ComponentManager that the component wants session management
to be provided.
session object: Each session is represented by a session object. This
object resembles a Map and can be used by the component to
store/retreive objects related to the session.
getSession(): The component is provided with a getSession() method which
retrieves the appropiate session object.
proxy: A proxy represents a component *and* a session. When the
client performs a lookup on the component it receives a proxy.
container+ComponentManager contract: if the client calls a method on a
proxy it received from lookup(), then getSession() will provide the
SessionEnabled component with the session associated with the client.
==== proposal (framework details) ====
provide the component with a SessionManager object that has a
getSession() method. e.g.
// first attempt (will change later)
interface SessionManager
{
/** @throws NoSuchSessionException (extends IllegalStateException,
* which is a RuntimeException).
*/
public Session getSession();
}
[if the container+ComponentManager fulfill their contract the
exception should never be thrown -- unless the component misbehaves
(see later)]
Add lifecycle interface SessionEnabled:
interface SessionEnabled
{
public void setSessionManager( SessionManager sm );
}
This method is to be called after initialize().
- This is not set in stone, however (see following note), calling
it after initialize reduces the potential for problems.
note: getSession() will not work during any lifecycle methods
(e.g. initialize, start/stop) -- as these are called directly on the
component by the container, and not by a client (via a proxy).
==== (proof of concept) implementation - not part of framework ====
This SessionStack interface may help seperate the container and
ComponentManger concerns:
interface SessionStack
{
public void push( Session session );
public Session pop();
public Session peek();
public boolean empty();
}
The following three classes are an example implementation:
class ProxyHandler extends InvocationHandler
{
Object m_component;
SessionStack m_sessionStack;
Session session;
public ProxyHandler(
Object component, SessionStack sessionStack, Session session
)
{
m_component = component;
m_sessionStack = sessionStack;
m_session = session;
}
public Object invoke( Object proxy, Method method, Object[] args )
throws Throwable
{
Object ret = null;
m_sessionStack.push( m_session );
try
{
return method.invoke( m_component, args );
}
catch ( InvocationTargetException ite )
{
throw ite.getTargetException();
}
finally
{
m_sessionStack.pop( m_session );
}
return ret;
}
}
class PerThreadSessionStack implments SessionStack
{
// note: probably a Stack implementation other than
// java.util.Stack would be preferable, since java.util.Stack is
// Vector based (which uses synchronization).
ThreadLocal m_stacks =
new InheritableThreadLocal()
{
protected Object initialValue()
{
return new Stack();
}
// if a component creates a new thread, the new thread gets a
// stack with only the current session.
protected Object childValue( Object parent )
{
Stack stack = new Stack();
if (! ((Stack)parent).empty() )
stack.push( ((Stack)parent).peek() );
return stack;
}
};
public void push( Session session )
{
((Stack) m_stacks.get()).push( session );
}
public Session pop()
{
return (Session) ((Stack) m_stacks.get()).pop();
}
public Session peek()
{
return (Session) ((Stack) m_stacks.get()).peek();
}
public boolean empty()
{
return ((Stack)m_stacks.get()).empty();
}
}
class SessionManagerImpl implements SessionManager
{
SessionStack m_sessionStack;
public SessionManagerImpl( SessionStack sessionStack )
{
m_sessionStack = sessionStack;
}
public Session getSession()
{
if (! m_sessionStack.empty() )
return m_sessionStack.peek();
else
throw new NoSuchSessionException( "Session not set" );
}
}
After the SessionEnabled component is initialized, an instance of
SessionStack is created - the component is then given an instance of
SessionManagerImpl based on this SessionStack.
Whenever a client looks up an instance of the component the following
occurs:
1. a new Session object is created
2. a new proxy is created with the component, SessionStack, and Session object
e.g.
proxy = java.lang.reflect.Proxy.newProxyInstance(
component.getClass().getClassLoader(),
component.getClass().getInterfaces(),
new ProxyHandler( component, sessionStack, session )
);
3. the proxy is returned to the client.
at this point whenever the client calls a method on the proxy, the
session is pushed onto the stack, and the component method is called
-- so now when the component calls m_sessionManager.getSession() the
session on top of the stack (for the current thread) is returned.
==== other issues ====
There is one potential problem -- if the component wants to do
something like:
someObject.setEventHandler( this );
the reason this is a problem is that 'this' refers to the component
and not to the proxy, went the callback is handled the session will be
undefined... to fix this problem I want to add another method to
SessionManager...
// second attempt
interface SessionManager
{
/** @throws NoSuchSessionException */
public Session getSession();
/** @throws NoSuchSessionException */
public Object getProxy();
}
where the getProxy() method returns an instance of a proxy which
contains the current session -- this could be the same object that was
passed to the client, or a new proxy could be created with the current
session.
the SessionManagerImpl (above) could implement this method as follows:
// the component is now passed into the constructor
// and saved in m_component
public Object getProxy()
{
ClassLoader classLoader = m_component.getClass().getClassLoader();
Class[] interfaces = m_component.getClass().getInterfaces();
Object handler = new ProxyHandler(
m_component, m_sessionStack, getSession()
);
return java.lang.reflect.Proxy.newProxyInstance(
classLoader, interfaces, handler
);
}
this allows the above call to be replaced with:
someObject.setEventHandler( (EventHandler) m_sessionManager.getProxy() );
and now the session is available when the callbacks are handled.
This does add some complexity to the component -- but this is small
compared to the session management complexity that can now be removed
from the component... the important thing is that the component must
never pass a direct reference to itself to another object (unless the
callback methods are completely session independant).
This shouldn't be too confusing for the component developers if the
SessionEnabled and SessionManager javadocs make it clear that "proxy =
component + session"
==== simple implementation ====
Of course if the container+ComponentManager doesn't want to bother
with all this it can simply do:
class SimpleSessionManager implements SessionManager
{
private Session m_session;
private Object m_component;
public SimpleSessionManager( Object component, Session session )
{
m_component = component;
m_session = session;
}
public Session getSession()
{
return m_session;
}
public Object getProxy()
{
return m_component;
}
}
in which case no proxy is needed [but with this impl a new component
needs to be created for each lookup].
Robert.
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>