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]>