001: /*
002: * Created on Nov 23, 2005
003: */
004: package uk.org.ponder.rsf.flow.lite;
005:
006: import uk.org.ponder.beanutil.BeanLocator;
007: import uk.org.ponder.hashutil.EighteenIDGenerator;
008: import uk.org.ponder.reflect.ReflectiveCache;
009: import uk.org.ponder.rsf.flow.ARIResult;
010: import uk.org.ponder.rsf.flow.FlowIDHolder;
011: import uk.org.ponder.rsf.processor.ErrorHandler;
012: import uk.org.ponder.rsf.request.ActionTarget;
013: import uk.org.ponder.rsf.viewstate.ViewParameters;
014: import uk.org.ponder.util.Logger;
015:
016: /**
017: * One instance of this bean is declared per flow in the request context. The
018: * "invoke action" phase of processing attempts to invoke a method on this bean
019: * named for the "action" method specified in the flow configuration, which is
020: * here decoded and converted into the required method call (or sequence of
021: * calls) on the beans specified in the relevant action states.
022: * <p>
023: * These beans are created in FlowProxyFactory, a useful convenience bean that
024: * prevents the user from needing to set the other tedious dependencies of this
025: * bean in addition to the actual target flow object.
026: * <p>
027: * Note that this bean causes the FlowIDHolder contents to be set with the
028: * respective flowID if it receives a START transition. This is a somewhat
029: * violation of BeanReasonableness, but we have not yet an architecture for flow
030: * scope construct-time activities.
031: *
032: * @author Antranig Basman (antranig@caret.cam.ac.uk)
033: *
034: */
035: public class FlowActionProxyBean implements ActionTarget {
036: // less hassle to keep this here than manage as a dependency....
037: private static EighteenIDGenerator idgenerator = new EighteenIDGenerator();
038: private Flow flow;
039:
040: private ReflectiveCache reflectivecache;
041: private BeanLocator rbl;
042: private ViewParameters viewparams;
043:
044: private FlowIDHolder flowidholder;
045:
046: private boolean strict = false;
047: private ErrorHandler errorhandler;
048:
049: // These two are user configured properties
050: public void setFlow(Flow flow) {
051: this .flow = flow;
052: }
053:
054: public void setStrict(boolean strict) {
055: this .strict = strict;
056: }
057:
058: // The remainder are system configured properties
059: public void setReflectiveCache(ReflectiveCache reflectivecache) {
060: this .reflectivecache = reflectivecache;
061: }
062:
063: public void setBeanLocator(BeanLocator rbl) {
064: this .rbl = rbl;
065: }
066:
067: public void setViewParameters(ViewParameters viewparams) {
068: this .viewparams = viewparams;
069: }
070:
071: public void setFlowIDHolder(FlowIDHolder flowidholder) {
072: this .flowidholder = flowidholder;
073: }
074:
075: public void setErrorHandler(ErrorHandler errorhandler) {
076: this .errorhandler = errorhandler;
077: }
078:
079: /**
080: * Called in response to invocation of a command link. The "method" name
081: * actually corresponds to the "on" text of the VIEW state. If the method name
082: * is FLOW_START, and the first state is a VIEW, we actually perform no action
083: * but simply redirect to the view, while noting that we want to start a flow.
084: * Note that the value returned from this method will be *immediately* fed to
085: * interpretActionResult above. If on the other hand we hit an ACTION state,
086: * we follow the chain, invoking actions as we go until we do hit a view
087: * state. The name of the resulting view state is the return.
088: */
089: public Object invokeAction(String name, String knownresult) {
090: State newstate;
091: ARIResult togo = new ARIResult();
092: ViewParameters resultingview = viewparams.copyBase();
093:
094: if (name.equals(ARIResult.FLOW_START)) {
095: if (!flowidholder.isEmpty()) {
096: throw new IllegalStateException("Flow " + flowidholder
097: + " already in progress, "
098: + "cannot be started");
099: }
100: flowidholder.setFlowID(flow.id);
101: flowidholder.setFlowToken(idgenerator.generateID());
102: newstate = flow.stateFor(flow.startstate);
103: if (newstate instanceof ViewState) {
104: flowidholder.setFlowStateID(newstate.id);
105: flowidholder.setRequestFlowStateID(newstate.id);
106:
107: togo.propagateBeans = ARIResult.FLOW_START;
108:
109: }
110: } else { // any action other than FLOW_START
111: // TODO: If this occurs on an ACTION cycle, should actually cause a VIEW
112: // redirect.
113: if (flowidholder.getRequestFlowStateID() == null) {
114: throw new IllegalStateException("Received flow action "
115: + name + " for Flow " + flow.id
116: + " without current flow state");
117: }
118: if (viewparams.endflow != null) {
119: throw new IllegalStateException("Received flow action "
120: + name + " for ended flow " + flow.id);
121: }
122: if (strict
123: && !flowidholder.getFlowStateID().equals(
124: flowidholder.getRequestFlowStateID())) {
125: throw new IllegalStateException("Flow " + flowidholder
126: + " received request" + " for state "
127: + flowidholder.getRequestFlowStateID()
128: + " whereas current " + " flow state is "
129: + flowidholder.getFlowStateID());
130: }
131: ViewState viewstate = (ViewState) flow
132: .stateFor(flowidholder.getRequestFlowStateID());
133: Transition trans = viewstate.transitions.transitionOn(name);
134: if (trans == null) {
135: throw new IllegalArgumentException(
136: "Could not find transition from viewstate with ID "
137: + viewparams.viewID + " for command "
138: + name);
139: }
140: newstate = flow.stateFor(trans.to);
141: }
142:
143: // continue while still actions - stop at either View or an EndState
144: while (newstate instanceof ActionState) {
145: ActionState actionstate = (ActionState) newstate;
146: Action action = actionstate.action;
147: Object bean = rbl.locateBean(action.bean);
148: if (bean == null) {
149: throw new IllegalStateException("Bean with id "
150: + action.bean + " not found in for action "
151: + actionstate.id);
152: }
153: String result = null;
154: Exception exception = null;
155: if (knownresult == null) {
156: try {
157: result = (String) reflectivecache.invokeMethod(
158: bean, action.method);
159: } catch (Exception e) {
160: exception = e;
161: }
162: } else {
163: result = knownresult;
164: knownresult = null; // consume the known result on the first occasion.
165: }
166: errorhandler.handleError(result, exception);
167:
168: Transition trans2 = actionstate.transitions
169: .transitionOn(result);
170: newstate = flow.stateFor(trans2.to);
171: Logger.log.info("Transition from action state "
172: + actionstate.id + " to state " + newstate.id
173: + " through result of " + result);
174: }
175: ViewableState viewstate = (ViewableState) newstate;
176: flowidholder.setRequestFlowStateID(newstate.id);
177: flowidholder.setFlowStateID(newstate.id);
178: resultingview.viewID = viewstate.viewID;
179: resultingview.flowtoken = flowidholder.getFlowToken();
180: togo.resultingView = resultingview;
181:
182: if (togo.propagateBeans == null) { // if not filled in as FLOW_START
183:
184: togo.propagateBeans = newstate instanceof EndState ? ARIResult.FLOW_END
185: : ARIResult.PROPAGATE;
186: }
187: return togo;
188: }
189: }
|