Folks,

Attached is a mini design doc I asked Ted to comment on before posting here.
His comments below are in response to it. I've taken the liberty of
responding to his responses. :)

Thanks for the feedback, Ted.

Donnie


-----Original Message-----
From: Ted Husted [mailto:[EMAIL PROTECTED]]
Sent: Saturday, December 01, 2001 9:39 AM
To: [EMAIL PROTECTED]
Subject: Opinion on design ideas???

Personally, I would approach this from a classic refactoring
perspective. Start by extracting methods and objects in the existing
code in a step-wise manner first, and then look to see if any sweeping
changes are needed. Often the code itself provides a lot clues as to
where the missing objects are hidden.

In the ActionServlet code, it's obvious that we need a "process" object,
that may be the equivalent to the pipeline object. Why? because there
are a great number of methods prefixed "process". Likewise, within the
code there are obviously a number of missing methods. Why? Because the
methods are very long, and heavily commented. Each major comment is a
proposition for "extract method".

Likewise, we obviously need an object to handle the growing number of
parameters coming in from the web.xml. Why? First, beacuse there of the
sheer number of "instance variables" with associated public setters, and
second because we have a method that reloads some of them. So, there is
definately a distinct entity here.

In other words, I would first let a better design "bubble up" from the
working code, so you can see the forest for the trees, and then see if
anything more drastic needs to be done.

>From a broader persective, I do like the idea of process factories. The
multipart hanlding stuff has run amock, and convulting a lot of
otherwise simple code for something that rarely happens. So I'd
definately to see the servlet able to branch to a "multipart" process
only when needed, and leave the "conventional" process streamlined.

Generally, I would say

1) There should definately be a StrutsConfig or ActionServletConfig
object to encapsulate what we read in from the web.xml. This may or may
not be used elsewhere, but we should definately document these as a
class.

2) In the near term, a wrapper around the objects we put in the contexts
would be helpful. They could still be stored as seperate attributes, but
a bean could be used to set and get them without knowing the attribute
names. Later this could evolve to seperate objects for seperate
controllers.

3) There should definately be a ActionServletProcess interface, and
associated base class implementations. The ActionServlet should be able
to choose between a conventional and multipart process now, and others
later. (Say, a VelocityProcess.)

<Donnie comment>
ActionServletProcess would be analogous to what I termed a "stage".
</Donnie comment>

4) The place where the decision takes place could also be an interface,
so you could load your own process dispatcher (which could then
reference whatever processes you needed). The current process method
could be the entry point for the ProcessDispatcher. The servlet passes
it the request and response, and it does the rest. All the other
process* methods could be extracted into another class.

5) Extract method should be used as much as possible on the base
classes. In particular, we should extract the autopopulation mechanism
so people can override this method with their own.

Meanwhile, I've reviewed some of your other postings, including those
on the Velocity list, and would like to make some comments.

*) I don't agree with the premise that the controller is
predisposed to forwarding because it presumes JSPs will be used. This is
a MVC framework, and it is really not the controller's job to render the
view. Hence, we forward on to someone who does.

<Donnie comment>
I understand that sentiment and, in general, agree with it. I just don't
think the underlying premise of "servlet = controller only" or "servlet =
view only" is necessarily valid. I think it's possible to have the
controller and view be decoupled but still run in the same servlet.
</Donnie comment>

*) A consequence of the refactoring is that it may be easier for a
developer to have a process render the view, as an Action may render it
now. This is fine, but I don't believe that this should be a primary
design goal.

*) I generally believe that templating mechanisms, like Velocity, should
be encapsulated in their own servlet. The controller doesn't know or
care whether it is forwarding to a *.jsp or *.vm, or something else
entirely.

*) I generally believe that the request context is the appropriate place
to pass data between the controller and the presentation servlets. At
this point, the objects Struts puts in these contexts should be
encapsulated by a wrapper (as they have grown in telling), but placing
these objects in these contexts *is* the correct general approach. They
then become available to any servlet anyone wants to write.

<Donnie comment>
I agree with that to the extent that the view will be in another servlet,
which means in the vast majority of cases. My primary concern with overuse
of the contexts is in things not directly related to the request / session
itself.
</Donnie comment>


*) I do not believe Struts has a JSP-bias as much as it has an API,
specification, and design pattern bias. And I believe that APIs,
specifications, and design patterns are Good Things. Struts tries
very hard to use existing technologies, like JavaBeans, serlvet
contexts,
and JSPs. When Java Faces comes out, I'm sure someone will want to
support those too. What Struts is really trying to do is bind these
technologies together, while creating a minimum number of helpers along
the way. Where an object was implied, but not provided, we have tried to
provide one (like MessageResources). Any other technology that can
access resources in the standard contexts is welcome to use them. That's
why they are there! The framework recognizes that other servlets may
also
be processing requests, and so makes resources available to anyone else
in the application who might need them.

*) Many of the people working with Struts now provide JSP hooks for
their objects. But the objects themselves are not tied to JSPs. David's
Validator is a good example. The backend engine could be used with any
technology. But since he's using it with JSPs, he exposed it that way.
Someone else could expose it with another technology, if they chose to
do so. Tag extensions are really just an interface to a JavaBean, and
any Java technology can access a JavaBean, if only to expose it through
another interface. Objects like an ActionForm, MessageResource, or
ActionMapping have no idea who's accessing them.
Struts "pipeline" enhancement
Donnie Hale
01 Dec 2001
=============================

Motivation
----------
This document describes an attempt to refactor the base Struts ActionsServlet
functionality in a number of ways. Here are some goals and constraints of the
effort:

- decouple Struts configuration handling from the code which behaves according
  to the configuration
- isolate the management and containment of the various Struts resources
  (Actions, ActionForms, etc.) from their usage during request processing
- decouple servlet from Struts processing steps
- ease possible support for multiple Struts servlets in a single context
- allow Struts applications to easily use as much or as little of Struts
  as required without incurring full request processing overhead
- recognize view mechanisms other than JSPs as "first class citizens"
- set stage for gradual elimination of all unnecessary items from the various
  servlet-related contexts
- minimal / no changes to existing Struts apps, particularly the examples,
  unless they've extended Struts in some way
- no changes to struts-config.xml except in support of enhanced functionality
- minimal, easy-to-do changes to web.xml

Design
------
This design sprung to mind based on different factors: recent discussions on
struts-dev (Nov 2001); attempts to elegantly integrate Struts with Velocity;
and extensive reflection on the current behavior of ActionServlet, especially
the "process()" method.

I took it as a given, based on list discussions, that configuration and the
management and access of Struts' "resources" (Actions, ActionForms, etc.) were
in need of refactoring.

I observed that each step in process() required examination as to whether
processing should continue. I observed that there were essentially 5 possible
states at the end of one of the "child" process methods (e.g.
processValidation): continue; return; error/exception; forward; include.
That led me to consider how that commonality could be more elegantly
abstracted.

The abstraction I arrived at would view each processing step as a "stage" in
the request processing "pipeline" (note that I hadn't read any Cocoon docs at
this point). No stage was viewed any differently from any other stage. The
collection of stages, the "pipeline", worked coherently together to completely
process a request. Stages were responsible for associating state with the
pipeline for use by later stages. The appropriate pipeline to use for a
request would be determined at runtime by asking all configured
"PipelineFactory" objects if they were responsible for a request. It could be
done by declarative mapping, but runtime allows the flexibility to perhaps
have a pipeline be picked based on content-type or something like that.

So then, the steps for processing a request, in pseudocode for the process()
method, would be:

public void process(HttpServletRequest request, HttpServletResponse response)
    throws ServletException
{
    Pipeline pipeline = null;
    for each pipeline factory, while pipeline is null
        pipeline = factory.wantToHandle(
            servletConfig, servletContext, request, response);
        
    pipeline.prepare();

    RequestStageResult result;
    for each stage in pipeline
        result = stage.execute();
        
        switch (result)
            case continue:
                next;
                
            case return:
                break done;
                
            case error:
                throw result.getServletException();
                
            case include:
                RequestDispatcher rd = result.getRequestDispatcher();
                rd.include(request, response);
                break done;
                
            case forward:
                RequestDispatcher rd = result.getRequestDispatcher();
                rd.include(request, response);
                break done;

:done

    finally
        pipeline.complete();

    return;
}

Importantly, the default behavior would be a single StrutsPipelineFactory,
configured as a side effect of a current-style struts-config.xml. It would
support all pipelines appropriate to what Struts does now. So multipart
requests, forwards, includes, and "normal" processing could all be part of a
single pipeline. Or the factory could return simpler ones for certain
requests.

These are the core interfaces / classes for the general-purpose pipeline
functionality:

- PipelineServlet
A servlet which handles request pipelines and stages as outlined above.
Requires a config file enumerating all PipelineFactories with their
configuration. Logically a singleton but should be able to be configured
multiple times, with different names, in web.xml.

- PipelineFactory
Interface. Checks a request and returns an appropriate pipeline if that
factory wants to handle the request. A single factory can support multiple
styles of pipelines by just constructing them as appropriate at runtime.
Each factory configured for the servlet is a singleton.

- RequestPipeline
An abstract base class representing a collection of stages. Perhaps contains
facilities supporting stages' need to pass state from stage to stage.

- RequestStage
An interface representing one step in the complete processing of a request.
Knows the pipeline of which it's a part. Has an "execute" or "perform"
method, the implementation of which fulfills the stage's part in the request
processing.

- RequestStageResult
See pseudocode above. Probably an interface. Supplies sufficient info to act
on result state (path for a forward/include, exception in case of an error).

These classes are part of the refactored Struts functionality:

- ConfigHandler
This class deals with all the Digester stuff. It's invoked, indirectly, in the
call chain of Servlet.init(). It requires a full path (or perhaps Stream) for
the config file and an instance of ResourceRepository. This is a singleton
with no state.

- ResourceRepository
At present, all the "stuff" created at startup and as part of configuration
handling is pretty directly a part of the ActionServlet. ResourceRepository is
the home for those things now (Actions, ActionForms, ActionMappings, etc.).
Multiple ResourceRepositories may exist, the appropriate one being accessible
via servlet name.

- StrutsPipelineFactory
Builds request pipelines which yield current Struts functionality. Uses
servlet name to locate appropriate ResourceRepository for its Actions,
ActionForms, etc. This is the entity that knows about the ConfigHandler and
ResourceRepositories. Those two classes are unknown to any of the base
PipelineServlet functionality.

- Various StrutsRequestStages
These implement the RequestStage interface. There would be one for all the
steps in the current ActionServlet.process() method. Ideally, existing Struts
resource classes (ActionForm, ActionMapping, etc.) would require no knowledge
of the StrutsRequestStages in which they played a part.


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

Reply via email to