Ted Husted wrote:

Craig R. McClanahan wrote:

In terms of the chain implementation of the request processing lifecycle, this is just an unconditional command in the chain (it's implemented in o.a.s.c.servlet.PerformForward). No skipping or stepping is requried. See

contrib/struts-chain/src/conf/chain-config.xml

for the entire definition. You'll note that it requires no skipping -- just the conditional behavior on validation failures, which is modelled as a branch to a separately named command.


I wonder if even these conditionals could be made part of the chain. As
an alternative to branching, the Command could update the state of the
Context and return false. What is now a conditional Command could be the
next link the Chain. The "conditional" would check to see if the state
calls for it to execute, and if so, do its business and return true.

They could indeed be made part of the chain, and "checking the current state to see if it's appropriate for me to do anything" is certainly more in the spirit of the original CoR pattern. However, I worry a little that creating the need for that state information increases coupling between commands, and therefore increases the complexity of reusing commands in alternative chains.


In the real-life use case we actually have in the CVS repository (the one in struts-chain, which actually does work :-), the case in question was to deal with the fact that the Struts request processing lifecycle has a branch in it, based on whether or not validation succeeds. The basic flow goes like this (using the class name of the command implementation class, and presuming we're all pretty familiar with the corresponding RequestProcessor behavior):

(1) LookupCommand (analogous to the processPreprocess() hook)
(2) ExceptionCatcher (no direct analog - used to implement exception mapping behavior)
(3) SelectLocale
(4) SelectAction
(5) CreateActionForm
(6) PopulateActionForm
(7) ValidateActionForm
(8) CreateAction
(9) ExecuteAction
(10) PerformForward


The conditional behavior happens in Step (7) -- if validation fails, an alternative path is desired:
(7a) SelectInput (uses the "input" attribute on the <action>)
(7b) PerformForward


At the moment, this is implemented as a branch, to a separate chain that the author of ValidateActionForm need not even know the name of at design time (it's a configuration property). If ValidateActionForm detects a failure, it looks up an alternate chain in the Catalog, executes this chain, and then returns true (so that steps 8-10 of the original chain are never executed). Note that step (10) in the original chain and step (7b) in the alternate chain share a single Command implementation instance, because Struts ends up doing the same thing either way (RequestDispatcher.forward() or redirect based on what ActionForward it is passed). Nothing had to be coded twice.

What would this look like if we merged it into a single chain? Probably something like this, with additional state-dependent checking indicated in [square brackets]

(1) LookupCommand
(2) ExceptionCatcher
(3) SelectLocale
(4) SelectAction
(5) CreateActionForm
(6) PopulateActionForm
(7) ValidateActionForm (must save state in the Context)
(NEW) SelectInput [skip if validation succeeded]
(8) CreateAction [skip if validation failed]
(9) ExecuteAction [skip if validation failed]
(10) PerformForward

So, implementing this as a single chain would require ValidateActionForm to the success/failure state of the validation into the Context -- easy to do -- and three commands must now (inside their implementation -- the Chain does not know anything about this) surround their current behavior in an "if" statement, which means that they have to know the location of the state information that was saved (slight increase in coupling), and they get processed by the Chain even if they are not relevant (very minor performance impact; almost never enough of a concern to worry about). But it would definitely work.

As Jing points out, the JavaServer Faces request processing lifecycle is slightly more complex, because there are more "go to the end" branchpoints (one after each processing phase) -- but again, either design pattern will work. In that case, conditional execution is actually easier because the relevant state (has FacesContext.renderResponse() been called yet?) is visible directly as part of the FacesContext that would be passed along anyway).

I think we're going to find that both approaches have applicability. You'll tend to choose the "branch" approach when the alternative behavior is completely different based on the branch conditions (think of a request processor for a SOAP request, which chooses the correct chain to execute based on the contents of the SOAP-Action header or other data in the request), while conditionals based on state will probably be better when the branches "merge" later on (i.e. the conditional stuff is in the middle of the chain, not at the end). But neither one of them require any change to commons-chain itself, and I'm going to continue to resist adding anything there until we find application requirements that are both (a) suitable for implementation as a chain in the first place; and (b) cannot be implemented cleanly without additional control flow type APIs.


-Ted.

Craig




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



Reply via email to