All,
the Context interface and its associated stage - Contextualizable - has
been the subject of much controversy. As a matter of fact, I'd say that it
is the single most controversial subject we have in Framework, and I'd like
to propose a way where I think the conflicting viewpoints can be
accomodated, although this will require some compromise.
-oOo-
First, there are two ways to view a context:
1) What I'll call the "Merlin" way, assumes that the context is an
unmodifiable map of constant, read-only data.
2) What I'll call the "Phoenix" way, extends the Merlin way by also
allowing operations in the Context, such as requestShutdown(). The Context
is here not just a source of deployment information, but also a link
through which the component may communicate with its container.
Second, the differences has given rise to two groups with differing viewpoints:
1) The goal of the Merlin group is component portability.
2) The goal of the Phoenix group is to be able to extend the Context as
needed - for example to turn it into a servlet context, ejb context, etc.
The *interests* of both groups are, I believe, to avoid having their code
become obsolete - a pure Phoenix approach would make the goal of component
portability unattainable, while a pure Merlin approach would make Phoenix
as it is now impossible.
Thus, any solution must be able to accomodate the current usage within
Phoenix, while still making component portability a
possibility. Specifically, any code that can't capture the usage pattern
in Phoenix is, in my opinion, dead on arrival.
I don't think it is possible to accomodate both sides 100%, but I do think
it is possible to have a solution where the Phoenix usage pattern is
allowed, while still keeping component portability for all practical
purposes. This means not 100% portability, but with very few cases of
non-portability, and a clear description of what is required for 100%
portability.
-oOo-
I'll focus on two things:
1) How does a component specify what context it requires?
2) How is this provided by the container?
SPECIFICATION
-------------
A brief overview of how it is currently solved (as I understand it):
Both cases:
The component can specify any key-value mappings it will access via the
Context.get method. The specification includes the key, optionally a name
translation meaning that you can, for example, access "avalon:work" through
the key "work", and a class name indicating the type of the value.
As for the actual type of the context instance being given to the component:
Merlin:
The component specifies a context class C. That class is instantiated with
the constructor taking one Context parameter. This instance is then given
to the component. (This was as I understood it from Stephen's emails, I
haven't found any code doing this in the assembly or meta packages, so I
might be way off here.)
Phoenix:
A BlockContext implementation is given directly to the component. No way
for the component to specify any other class.
Note that both containers completely solve the problem so far as to having
a way to specify the expected key-value mappings accessible in the Context.
What is needed is a way to specify what methods should be avilable in
addition to a way to specify key-value mappings accessible via the
Context.get method. This is needed in order to be able to capture the
Phoenix BlockContext interface. With both meta-models, you can specify a
requirement for any key K to map to an object of any type V, and using the
standardized context keys, you can specify the meaning of the value V.
However, neither allows you to require a method called requestShutdown()
that requests a shutdown. While Merlin allows you to specify an
implementation class, that method can not be used to provide a BlockContext.
What I intend to add is the following:
+ A way to specify what mehods are required in the context.
+ A restriction on what methods may be required while still remaining
100% Avalon compatible.
How to Specify Methods:
The component will declare one class name designating an interface. This
can be done like this:
<context>
<require-interface name="org.apache.avalon.phoenix.BlockContext"/>
</context>
This indicates that the context object being passed to contextualize() must
be cast-able to a BlockContext. This is read as: "The component requires
that all methods in the BlockContext interface is present in the context,
and that the context object given can be casted to a BlockContext."
A Restriction on Methods:
There will be a set of standard interfaces in Framework. Any component may
request any union of those interfaces. For example, if we have in framework:
interface WorkDirectoryContext {
public File getWorkDirectory ();
}
interface ContextDirectoryContext {
public File getContextDirectory ();
}
A component may have an interface:
interface MyContextInterface
extends ContextDirectoryContext,
WorkDirectoryContext
{};
And may specify that interface:
<context>
<require-interface name="org.example.MyContextInterface"/>
</context>
And can expect to have the request fulfilled in any 100% Avalon container.
(Alternatively we can limit interfaces to Avalon Micro Edition, SE, or EE,
depending on the profiles we come up with for the übercontainer.) Note
that this does in no way exclude specifying key-value pair requirements. A
component can specify key-value pairs, an interface, both or neither.
A final restriction on the methods are that the method signatures must be
unique. That is, if we in framework have two interfaces:
interface WorkDirectoryContext {
public File getDirectory ();
}
interface ContextDirectoryContext {
public File getDirectory ();
}
With identical signatures, a union of those interfaces
interface MyContextInterface
extends ContextDirectoryContext,
WorkDirectoryContext
{};
will only have one method, and furthermore it is *impossible* to determine
through which interface a call was made. That is:
MyContextInterface mci = ...;
// These two method calls are indistinguishale.
// There is no way, even with dynamic proxies,
// for the mci object to know whether the context
// directory or the work directory should be returned.
((WorkDirectoryContext) mci).getDirectory ();
((ContextDirectoryContext) mci).getDirectory ();
We can ease that restriction by only requiring method signatures in the
interfaces in Framework to be unique, but this would make it harder to
promote a method into Framework. Obvious conclusion: these context
interfaces should be kept to a minimum.
What we allow in Framework:
It is my view that the methods in Framework should be limited to simple
data-access methods, such as getWorkDirectory and getContextDirectory, and
that methods such as requestShutdown should be left out. The reasoning
behind this is as follows:
+ There is major controversy regarding the exposure of services, such as
requestShutdown (in particular that one).
+ Few components in Phoenix uses that method.
+ In fact, I think most Phoenix blocks only use the getContextDirectory
method.
+ Therefore, we can lower the requirements on those blocks, thus making
them portable.
+ For the few blocks that *do* require a requestShutdown or similar, they
can declare a requirement of BlockContext.
+ Those few blocks will remain non-portable, but I guess they are so few
that it doesn't matter.
Also, it is my opinion that:
+ Addition of a context interface to Framework should be via consensus vote.
+ Domain-specific contexts, such as EJB contexts and servlet contexts,
should not be allowed into Framework, as neither EJBs nor Servlets are
Avalon components.
-oOo-
PROVIDING A CONTEXT
-------------------
In the previos section I established that a component may place two types
of requirements on a context:
1) Key-value mappings.
2) Methods in the context interface.
How to provide (1) is a solved problem and there is concensus on it.
As for (2), I expect the container to have some class that implements Context:
class ContextImpl implements Context { ... }
That class should implement *all* Framework-level interfaces:
class ContextImpl implements Context,
WorkDirectoryContext, ContextDirectoryContext { ... }
It can also implement any other interfaces, but this is trivial:
class ContextImpl implements Context,
WorkDirectoryContext,
ContextDirectoryContext,
ShutdownContext { ... }
OK, given the above in the container, what happens when a component
requests a Context interface like this:
interface MyContextInterface
extends ContextDirectoryContext,
WorkDirectoryContext
{};
<context>
<require-interface name="org.example.MyContextInterface"/>
</context>
Note the following: Just because ContextImpl implements
ContextDirectoryContext and WorkDirectoryContext, it does *not* implement
MyContextInterface. The following code will fail with a ClassCastException:
ContextImpl impl = new ContextImpl ();
MyContextInterface mci = (MyContextInterface) impl; // ClassCastException
Thus, we need to use a proxy object implementing the required context
interface. The process for a container is as follows:
1. Load the class specified in the <require-interface/> element. In this
case it is MyContextInterface.
2. For each MI in the methods in the MyContextInterface interface:
2.1. Find a method, M, with the same signature in the container's
(corresponding) ContextImpl class.
2.2. If a method isn't found, give up and throw an Exception
(ComponentNotSupported)
2.3. Otherwise, establish a mapping MI -> M.
3. Create a dynamic proxy implementing MyContextInterface.
4. Let the InvocationHandler map every call via the mapping established
in step 2.
5. Give this proxy to the component's contextualize() method.
-oOo-
Summary:
I have shown a way to declare a context requirement for components that
captures all current usage patterns, and shown how the requirement can be
satisfied by a container. The methods shown here can be used to define
ServletContexts, EJB contexts, etc. as well. In particular, they can be
used to define the Phoenix BlockContext and the JAMES MailContext.
Portability suffers a little, but not enough to make it an issue.
/LS
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>
- Re: [PROPOSAL] Context Defined Leo Sutic
- Re: [PROPOSAL] Context Defined Peter Donald
- RE: [PROPOSAL] Context Defined Noel J. Bergman
- Re: [PROPOSAL] Context Defined Peter Donald
- RE: [PROPOSAL] Context Defined Noel J. Bergman
- Re: [PROPOSAL] Context Defined Chad Stansbury
- Re: [PROPOSAL] Context Defined Peter Donald
- Re: [PROPOSAL] Context Defined Stephen McConnell
- Re: [PROPOSAL] Context Defined Paul Hammant
- Re: [PROPOSAL] Context Defined Stephen McConnell
- Dissent [was [PROPOSAL] Context Defined] Paul Hammant