cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Brett McLaughlin <bmcla...@algx.net>
Subject Using Cocoon from other Java code (very long)
Date Thu, 16 Dec 1999 16:16:19 GMT
All-

	First off, let me apologize up front for sending this to the list and
some individuals on Turbine; however, this is a really significant
change I am trying to get in place.  That being said...

	I have been working on allowing Cocoon to be used from more than just
the Cocoon front-end servlet.  For example, in Turbine, screens may
generate all their (XML) content, and want to send those screens through
to Cocoon to process.  Right now (at least with the code in CVS) this is
impossible, particularly in JSDK 2.0 with no RequestDispatcher.include()
or RequestDispatcher.forward().  In addition, there are some very tricky
issues to do with Cocoon expecting some things in the HttpServletRequest
that another application couldn't put in there on the fly (there are no
addXXX or setXXX methods on ServletRequest).

	So here is my solution.

First: With all the changes, everything in Cocoon WORKS AS IS.  Again,
not a single line of producer/processor custom code has to change.

So here it is.  The first things that happens is that Cocoon starts up
(via init() in the servlet) and initializes the Cocoon Engine.  Now this
is something we want shared - in other words, nothing but the Cocoon
servlet should ever start up the engine, like with a
Class.forName().newInstance(), because all the parameters aren't set,
and Cocoon handles that nicely.  However, there is never a need for more
than one Engine instance for a given JVM/servlet zone.  So based on
that, I made Engine a singleton.  Instead of doing:

Engine engine = new Engine(confs,
this.getServletConfig().getServletContext());

it now does

Engine engine = Engine.getInstance(confs,
this.getServletConfig().getServletContext());

The getInstance() method takes care of instantiating the class, and
storing an internal reference to it.  Now, another application wanting
to use Cocoon processing can do:

Engine cocoonEngine = Engine.getInstance();

This no-args version throws an exception if the Cocoon servlet has not
already initialized the engine with all of the correct information (so
Cocoon must be running as a servlet for any of this to work).

So now, I can call <code>cocoonEngine.handle(req, res);</code> from
other applications, and get the benefits of Cocoon processing.  This is
the first step.

And again, Cocoon works as is, no changes to modules, producers,
processors.

Next: The next problem is that of the HttpServletRequest object.  As I
mentioned, all this doesn't help much, because the request coming in to
Turbine or some other object doesn't contain the information that Cocoon
needs.  Also, for a standalone application, even if they get a new
instance of HttpServletRequest to meet the method parameters, it doesn't
tell Cocoon what it needs to know.  So how to address this?  Well, lots
of changes in Engine.java, but none that are as bad as you may think.

Allow a new method: <code>public void handle(HttpServletRequest request,
HttpServletResponse response, Hashtable params) throws Exception;</code>

This is just what it looks like - another version of handle that allows
the passing in of a Hashtable of additional parameters.  This is what
other applications should be able to do to specify to Cocoon what to
use.  However, the request object _always_ takes precedence.  If params
is null, it is pretty much ignored.  I made changes to all the helper
functions like getKey() to check the request, and if the key was not
found and the Hashtable wasn't null, check there.  Then behave
normally.  This basically takes care of allowing other applications to
pass in to Cocoon things to process on (like the "user-Agent", the
producer to use, etc.)

You will see all of these changes in Engine.java (diff included), but
you will notice that in total, for already running apps using Cocoon as
it was originally designed, it only adds about 5 cycles - one for each
<code>if</code> statement to check on the Hashtable being null.  When
it's null, it goes on.  And these checks only occur if a parameter isn't
specified in the request object, so when that happens, there is almost
no difference at all.  The only complex issue here is the cache.  It is
tough to determine if a page is in cache when the page is being
requested by a combination of the request object and the Hashtable
parameters.  For now, I don't check for cache if Hashtable parameters
are present.  This could be done later.

I also draw the line after the initial producer is used for checking
Hashtable params.  Things like specifying a processor can all be done
within the producer in the document, so for now (and simplicity), the
producer should take care of that.  This also speeds processing up, as
we aren't checking anything differently after the producer changepoint.

Next:
So all that's left is the producer portion of things.  Presumably, an
application would create it's own content and shove it into the
Hashtable.  It could alternatively pass instructions in, and write a
producer that would receive those instructions and do all sorts of other
interesting things.  After the document is parsed, it is back to normal
Cocoon operation, and we are in good shape.

So I had to make some producer changes to accomodate this.  Again, all
existing producers will work AS IS (I tested this numerous times).  

First, there needs to be a way for ProducerFactory to see if a producer
was requested in the Hashtable params.  THis was a minor change, similar
to the Engine - the params are only checked if there is nothing
requested in the HttpServletRequest, and the params are not null.

Then, a producer should have a getStream(HttpServletRequest,
HttpServletResponse, Hashtable) to pass in the additional
application-set parameters and getDocument(<same three params>) that
overload the current implementations.  This allows the producer to get
access to all the things set by the application.

Finally, the AbstractProducer makes this all work like a dream.  The
getDocument method stays pretty much as it, just add an overloaded
version to accept a Hashtable.  That then calls getStream(request,
response, params).  This method, because it is new, could cause
producers that already exist problems, but the nice OO design saved the
day (nice job, Stefano & Co.).  I added a method getStream(request,
response, params) into AbstractProducer that takes the request, simply
drops off the Hashtable, and calls and returns the result of
getStream(request, response).  This means that all existing producers
work as is, same methods get called, etc.

_however_, I can now easily write a Producer, say StreamProducer, that
overrides that getStream() with the Hashtable and pulls out information
from that, creates a document, and returns it.  Viola!  Cocoon from a
non-Cocoon application.

Please look these over, it really works great!  I included all the
needed diffs, and will work on an example Producer and sample
application to show how it is used.  Sorry for the long post and the
multiple diffs.

I know this many seems like a lot of changes, but when you look at the
code, it is really minor stuff - the design allowed for it all very
easily.  Comments?

-Brett
Mime
View raw message