The work I have been doing over in Cocoon town (piece by piece) has led me to accept certain requirements of request/response style systems.
* The system must adapt to the current environment, which may include parameters for a particular request or a session.
* Course grain components like Servlets handle this remarkably well because the request/response/session information is passed to the component with each request.
* Processing requirements and usage requirements are two totally different things. For example, a published interface for a component only has the client interface in mind. However, that is not the only picture.
* A component might change its response based on its client. For example, the "cocoon:" psuedo protocol in Cocoon maps relative paths to the sitemap that specified the "cocoon:" URL. Traditional container heirarchies don't map well to this problem.
These are challenges in many component frameworks, and they are not unique to Avalon. So what should we do about it? It is not an easy question to answer. Finding a generic solution to specific problems is kind of the holy grail that we aspire to. (well ok, maybe its just me ;P)
So how do we even begin to look at this problem. The simple answer is to get back to basics. One of the most important design patterns we use is Multi- Dimensional Separation of Concerns (MDSOC). We can call it SOC for short. It is the foundational principles that drove the development effort behind aspect programming.
Right now, we have identified that we have an interface and a component implementation. That interface, which represents the code portion of the role, specifies the _client_ concerns. It is the interface that we expect to use regardless of what happens behind the scenes. We have a number of interfaces that represent different _lifecycle_ concerns. We have the ability to bring a component from the initialization phase to the active phase, and then finally to the destruction phase. However, what we don't handle well is changes to the environment in the active phase.
The Re* interfaces don't properly identify the concerns that happen when a component is running. The Recompose interface is a hack, as the lookup interface (the ComponentManager or ServiceManager) should be able to adapt. However, it seems to be the only solution to Cocoon's little issue with resolving the "cocoon:" interface. The Reconfigurable interface might be useful in some contexts, but most cases that needed to change their configuration at runtime handle that aspect themselves, and they only need a way to persist the changes. Recontextualizable also has some issues with it as well.
The whole thing with the re* interfaces is that they allow other containers to set those artifacts--or at least that is the only use found for them so far. I find that a security issue as well as a logistical one. If we release a component to the new ServiceManager it was given, will it really be released properly? We don't have that guarantee. It may, but it may not.
I think one possibility that we haven't looked at is the fact that a component does not necessarily have to directly implement an interface if there is a reasonable direct translation. For example, the two following interfaces are for all intents and purposes similar enough:
package org.apache.avalon.test;
interface EchoServer
{
String echo(String input);
}package com.mybiz.foo.test;
interface PingServer
{
String echo(String input);
}Now, we can have a component that does what we need for the EchoServer, but our system is looking for a PingServer. We could easily enough proxy our EchoServer implementation but implement the PingServer interface. Even the method names are the same. Its a nice way to wrap components without manually writing the facade. It is also a nice way to have component implementations in another class heirarchy proxied into the one you are working in.
What would happend if we take this simple trick and adapt it for our use?
For example, let's say we want the EchoServer above to be our client interface, but we want the implementation to adapt to the environment.
We could provide some complex ThreadLocal management and Context manipulation, with a little bit of voodoo mixed in for good measure. I tend to think this solution is a bit too complicated for someone to come in and understand it.
Now, imagine if we have a "parameterized" EchoServer that looked like this:
package org.apache.avalon.test;
interface RequestBasedEchoServer
{
String echo(Request request, String input);
}The client does not have the Request object, nor should we force it to. That type of contract is fragile. The thing is that we could easily tool this type of proxy. All we are doing is adding a "Request" parameter before the rest of the methods. Any state information would be stored with the Request, or a Session object accessible from the request.
So far, so good. However, how do we resolve this Request object, or customize it depending on where we are with the system at large?
Hopefully, experience with Servlets has taught us something. Essentially we have one Request object for the life of the entire request. One session object for the life of the entire session. The next question is how do we know that the Request object won't be corrupted as we are processing things. In most applications, this is not really much of an issue. Most servlet developers are pretty good about respecting the values that they store in the request/session objects.
However, when the larger "component/container" question comes into the picture, we very well may use third party components. How do we ensure they don't mess up other people's stuff? Secondly, is it really our responsibility? The more I think about it, I really think that we can overthink that problem. We can suggest a naming scheme like Sun did for Java packages, and that would be enough. Any manipulations of the Request object values would have to be a documented part of the component's contract. In systems like Cocoon, the sitemap root can be customized so that as the request is passed from sitemap to sitemap, that value is set, or even a stack implementation would be used.
That would be enough for most things. In fact, such a system would be truly powerful.
Of course another variation of this approach is to have a SessionEnabled interface with a setSession and an unsetSession. The proxy would call the set and unset methods surrounding each method call. The whole process would have to be synchronized, which could introduce some bottlenecks. However, on the plus side, we would have singleton components that adapted to what was currently going on, making the whole concept of pooling unnecessary.
--
"They that give up essential liberty to obtain a little temporary safety
deserve neither liberty nor safety."
- Benjamin Franklin--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
