Package javawebparts.misc.chain
This package contains a Chain Of Responsibility (CoR) pattern implementation
which offers a great deal of flexibility.
See:
Description
Interface Summary |
Command |
This is the interface that a Command must implement. |
Class Summary |
Catalog |
This class represents a Catalog. |
Chain |
This class represents a Chain. |
ChainContext |
This class represents a ChainContext. |
ChainManager |
This class is the top of the hierarchy, and all client application
interaction with the Chain implementation should occur through this class. |
CommandConfig |
This is the class that represents the configuration for a Command. |
Result |
This class is returned by a Command or Chain. |
Package javawebparts.misc.chain Description
This package contains a Chain Of Responsibility (CoR) pattern implementation
which offers a great deal of flexibility. Note that this implementation
IS NOT web-specific, that is, you can use it outside a webapp without any
problem.
As defined by the Gang Of Four (GoF - Gamma, Helm, Johnson, and Vlissides),
the CoR pattern can be described like so:
"Avoid coupling the sender of a request to its receiver by giving more than
one object a chance to handle the request. Chain the receiving objects and
pass the request along the chain until an object handles it."
Yeah, whatever :) In short, we're talking about defining a series of atomic
operations that are "chained" together and executed in sequence, giving
each step the opportunity to do something or not.
As an example, take the simple mathematical equation:
1 + 2 + 3 + 4 = 10
We can break this into a series of individual steps. In this description,
the letter A represents the accumulation of the result after each step:
Step 1. A = 1 + 2
Step 2. A = A + 3
Step 3. A = A + 4
Now, imagine you defined each of those steps as a unique Java class. Further
imagine that you declared that those three steps, done in sequence, is called
a Chain, and this Chain represents the larger operation of the equation.
In short, that's the CoR pattern.
Now, on to the details of this implenentation...
Simply put, you will create an XML file something like the following:
<chainConfig>
<catalog id="myCatalog">
<chain id="myChain">
<command id="step1"
className="my.app.Step1" />
<command id="step2"
className="my.app.Step2" />
<command id="step3"
className="my.app.Step3" />
</chain>
</catalog>
</chainConfig>
The config file must be in the classpath accessible to the ChainManager
class! In a webapp, the easiest way to do this is to place the file in
WEB-INF/classes. By default, the name of the file MUST be chain_config.xml.
You can override this in two ways: (a) pass the filename (which is
technically a path) to the ChainManager's constructor, or (b) set an
environment variable named JWP_CHAIN_CONFIG_FILE_NAME to the value you
desire. If you have any question about how to set this value, look up the
getResourceAsStream() method of the Class class in the Java SDK. The rules
enumerated there are what you must follow.
The config file defines three elements: Catalogs, Chains and Commands.
Catalogs are nothing but collections of Chains who's sole purpose is to
allow you to organize things better when you are defining multiple Chains,
and a Chain is of course an ordered series of Commands to be executed.
The thing you as a developer will be primarily concerned with are the
Commands. These define individual steps in a Chain, and this is what you will
implement in code. Let's take a look at the three Commands above, which
correspond to the three steps in that equation from before:
package my.app;
import javawebparts.misc.chain.ChainContext;
import javawebparts.misc.chain.Command;
import javawebparts.misc.chain.Result;
public class Step1 implements Command {
public Result init(ChainContext chainContext) {
return new Result(Result.SUCCESS);
}
public Result execute(ChainContext chainContext) {
int a = 1 + 2;
chainContext.setAttribute("a", new Integer(a));
return new Result(Result.SUCCESS);
}
public Result cleanup(ChainContext chainContext) {
return new Result(Result.SUCCESS);
}
}
Every Command you write will implement the Command interface, which defines
three methods: init(), execute() and cleanup().
You can think of init() exactly like you do a constructor in that it will
always be called before anything else. You can do whatever you like here...
grab a database connection, call some remote system, initialize some fields,
etc. Note that Commands do not have to be thread-safe because a new
instance of it is instantiated every time it is needed. Of course, this
means you cannot store things in the instance between Command executions.
You can however store them between the execution of these three
methods, you don't have to resort to static fields unless you want to.
The cleanup() method can be thought of like finalize() except that it is more
deterministic in that it will always be called as the last thing done,
regardless of what happens elsewhere in the Command (well, except for
exceptions, which could cause it to not execute).
The execute() method is where the actual work of your Command should go.
Each of the three methods receives a reference to a ChainContext object.
The ChainContext is how information is communicated between Commands of the
Chain. Conceptually it is just like an HttpRequest object. In fact, you
work with it in the same way via setAttribute() and getAttribute() methods,
which takes a key and an Object value to store. You can put whatever you
like in the context, at any time (i.e., from any of the three methods, and/or
before and after the Chain executes). You can also find some standard pieces
of information in the context, such as the last Result that was produced by a
Command (or a Chain, if a Command is a subchain... more on this later) as well
as the ID of the Catalog and Chain the Command belongs to.
Each of the three methods returns an instance of the Result class. This
defines what happened within the Command. You cnn construct a Result object
in one of three ways:
Result r = new Result(Result.xxxx);
In this case, you are just returning one of the allowed result codes as
defined by the static fields in the Result class, represented by the xxxx
above. Those values are:
- SUCCESS - Return this when everything went according to plan and
the chain should continue normally.
- FAIL - Return this when an unexpected failure occurs. In
general, your Commands should never throw exceptions! You should
handle any exception that could happen and return this code if it is
something the Command cannot handle and the Chain should be stopped.
- ABORT - This is returned when the chain should be stopped. This
is not the same as FAIL because FAIL indicates conditions that
should really never happen, where ABORT are conditions that you
expect could reasonably happen.
- RESTART_CHAIN - As you would think, the Chain is restarted from
the beginning. Note however that the ChainContext is not reset!
Also note that this is can lead to an infinite loop, so you have to
take care to ensure that can never happen!!
- REDO_COMMAND - Re-executes the Command. Note that this too is a
prime candidate for causing an infinite loop, so be careful!!
- JUMP_TO_COMMAND - This acts like a GOTO statement in BASIC.
Result r = new Result(Result.xxxx, "yyyy");
The result code is the first parameter and is still as described above. The
"yyyy" parasmeter is any arbitrary extra info you would like to return. This
can be helpful if the next Command should do something in a variable way
based on what the result of the previous Command was. Note that if your
intention is to return some extra info to the caller executing the Chain,
the extra info can only be returned by the last Command in the Chain because
subsequent Commands would effectively overwrite any previous extra info.
Alternatively, each Command in your Chain would have to replicate the
extra info along the Chain so it is always present for the last Command to
return.
Result r = new Result(Result.xxxx, "yyyy", "zzzz");
The result code and extra info parameters are still as described above. The
"zzzz" parameter is the ID of a Command. This is only applicable when the
return code is JUMP_TO_COMMAND.
Let's look at the next Command in the chain:
package my.app;
import javawebparts.misc.chain.ChainContext;
import javawebparts.misc.chain.Command;
import javawebparts.misc.chain.Result;
public class Step2 implements Command {
public Result init(ChainContext chainContext) {
return new Result(Result.SUCCESS);
}
public Result execute(ChainContext chainContext) {
Integer a = (Integer)chainContext.getAttribute("a");
a = new Integer(a.intValue() + 3);
chainContext.setAttribute("a", a);
return new Result(Result.SUCCESS);
}
public Result cleanup(ChainContext chainContext) {
return new Result(Result.SUCCESS);
}
}
Still very straight-forward. No sense wasting the space to show the third
Command, it's the same except we're adding 4 to A in it.
So, at this point we've coded up the Commands, defined them to form a Chain
in the config file, and added the Chain to a Catalog. All that's left is
using it. Very simple:
ChainManager cm = new ChainManager();
ChainContext ct = cm.createContext();
cm.executeChain("myCatalog/myChain, ct);
Result cr = ct.getResult();
if (cr.getCode() == Result.SUCCESS) {
System.out.println(ct.getAttribute("a"));
} else {
System.out.println("Chain FAIL or ABORT");
}
All interaction with Chains are done through the ChainManager. So, we need
to instantiate one. The first time a ChainManager is instantiated, the config
file will be read and static objects created to represent it. Subsequent
ChainMaager instantiations will not incur this overhead. Once we
have a ChainManager, we ask it for a new ChainContext object. You can at
this point add whatever initial data to it tha your Chain needs. Then, we
simply ask it to execute a given Chain in a given Catalog, handing it the
ChainContext. Once it completes, we interrogate the ChainContext to see
what the result of the execution was, and act accordingly.
And that, in a nutshell, is the Chain implementation in Java Web Parts!
Ok, so that's the basic. How about the more advanced stuff?
- Commands can themselves be Chains (AKA, Subchains)
Take the standard Command definition in the config file:
<command id="step2" className="my.app.Step2" />
Let's say that instead of doing A=A+3 as the second step, we instead
wanted to do (A=A+6)*2. We could of course just break that into two
Commands and add them to the Chain, but another approach is to create
those Commands and make them their own Chain, then make the second step
of the original Chain be that new Chain. The Commands would be exactly
as before, no change whatsoever. The config file is different though,
and here's what it would now look like:
<chainConfig><br>
<catalog id="myCatalog">
<chain id="myChain">
<command id="step1"
className="my.app.Step1" />
<command id="step2"
chain="myChain2" />
<command id="step3"
className="my.app.Step3" />
</chain>
<chain id="myChain2">
<command id="step2a"
className="my.app.Step2a" />
<command id="step2b"
className="my.app.Step2b" />
</chain>
</catalog>
</chainConfig>
Let's assume that Comamnd Step2a does A=A+6 and Step2b does A=A*2. Now,
as defined, myChain2 will be executed when myChain hits step 2. When
myChain2 completes, myChain will continue at step 3. That's all there is
to it! Of course, you can execute myChain2 on its own if you need to,
it's just another Chain in the Catalog.
- Chains can extend other Chains
You can do this in the config file:
<chain id="myChain2" extends="myCatalog/myChain">
You will always specify extends in the form xxxx/yyyy where xxxx is the
Catalog ID and yyyy is the Chain ID. When you do this, the contents of
the base Chain is copied into the extended Chain, before any new
Commands are added. So, if you had this config file:
<chainConfig><br>
<catalog id="myCatalog">
<chain id="myChain">
<command id="step1"
className="my.app.Step1" />
<command id="step2"
className="my.app.Step2" />
<command id="step3"
className="my.app.Step3" />
</chain>
<chain id="myChain2"
extends="myCatalog/myChain">
<command
id="step4" className="my.app.Step4" />
<command
id="step5" className="my.app.Step5" />
</chain>
</catalog>
</chainConfig>
...Then the sequence of Commands in myChain2 would be step1,step2,step3,
step4,step5. Note that these are COPIES! In other words, it is as if you
defined the three Commands from myChain directly in myChain2. At runtime,
there is essentially two instance of step1 floating around, one tied to
each of the two Chains.
- Commands can be replaced when extending a Chain
If we replace the above line:
<command id="step4" className="my.app.Step4" />
...with...
<command id="step4" className="my.app.Step4" replaceid="step2"/>
...then the final sequence of Commands in myChain2 would be step1,step4,
step3,step5. The step4 Command replaces the step2 Command. But again,
remember these are COPIES, so the sequence of Commands in myChain is
STILL step1,step2,step3. This provides something very much like the
ability to override methods in a class you extend within the Chain
paradigm.
- Catalogs can extend other Catalogs
There is an extends attribute on the Catalog element, just like on the Chain
element (except that it is in the form "yyyy" where yyyy is the ID of the
Catalog you wish to extend), that serves the exact same purpose. All the
Chains in the base Catalog are copied to the extended Catalog. Any Chain
then defined in the extended catalog with an ID that matches a Chain copied
over will override the copied Chain. Note that for a Catalog to exend
another, the base Catalog must have been defined prior to the extends
attribute appearing in the config file! In other words, if you have
three Catalogs defined named Catalog1,Catalog2,Catalog3 and they are defined
in that order, than Catalog2 or Catalog3 could extend Catalog1, but Catalog1
could not extend anything, and things will blow up if you try it!
That about covers it. I hope you find the CoR implementation useful! There
is some further advanced functionality I intend to add, but for now, this
is it. Enjoy!
This package depends on the following extra packages to compile and run: None.
Copyright © 2005 Frank W. Zammetti